Skip to content

Commit

Permalink
Do not remove body of directives known to contain reStructuredText
Browse files Browse the repository at this point in the history
  • Loading branch information
JulienPalard committed Oct 18, 2022
1 parent d24fdad commit a419948
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 15 deletions.
3 changes: 3 additions & 0 deletions download-more-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
# https://github.com/sympy/sympy doc
# https://github.com/sphinx-doc/sphinx doc --enable line-too-long --max-line-length 85

# This one could be enabled soon:
## https://github.com/python/python-docs-fr . --enable all --disable line-too-long

grep '^# https://' "$0" |
while read -r _ repo directory flags
do
Expand Down
48 changes: 33 additions & 15 deletions sphinxlint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

"""Sphinx rst linter."""

__version__ = "0.6.6"
__version__ = "0.6.7"

import argparse
import io
Expand Down Expand Up @@ -77,34 +77,50 @@
)

# fmt: off
DIRECTIVES = [
DIRECTIVES_CONTAINING_RST = [
# standard docutils ones
'admonition', 'attention', 'caution', 'class', 'compound', 'container',
'contents', 'csv-table', 'danger', 'date', 'default-role', 'epigraph',
'error', 'figure', 'footer', 'header', 'highlights', 'hint', 'image',
'important', 'include', 'line-block', 'list-table', 'meta', 'note',
'parsed-literal', 'pull-quote', 'raw', 'replace',
'restructuredtext-test-directive', 'role', 'rubric', 'sectnum', 'sidebar',
'table', 'target-notes', 'tip', 'title', 'topic', 'unicode', 'warning',
'danger', 'epigraph', 'error', 'figure', 'footer', 'header', 'highlights',
'hint', 'image', 'important', 'include', 'line-block', 'list-table', 'meta',
'note', 'parsed-literal', 'pull-quote', 'replace', 'sidebar', 'tip', 'topic',
'warning',
# Sphinx and Python docs custom ones
'acks', 'attribute', 'autoattribute', 'autoclass', 'autodata',
'autoexception', 'autofunction', 'automethod', 'automodule',
'availability', 'centered', 'cfunction', 'class', 'classmethod', 'cmacro',
'cmdoption', 'cmember', 'code-block', 'confval', 'cssclass', 'ctype',
'cmdoption', 'cmember', 'confval', 'cssclass', 'ctype',
'currentmodule', 'cvar', 'data', 'decorator', 'decoratormethod',
'deprecated-removed', 'deprecated(?!-removed)', 'describe', 'directive',
'doctest', 'envvar', 'event', 'exception', 'function', 'glossary',
'highlight', 'highlightlang', 'impl-detail', 'index', 'literalinclude',
'method', 'miscnews', 'module', 'moduleauthor', 'opcode', 'pdbcommand',
'productionlist', 'program', 'role', 'sectionauthor', 'seealso',
'program', 'role', 'sectionauthor', 'seealso',
'sourcecode', 'staticmethod', 'tabularcolumns', 'testcode', 'testoutput',
'testsetup', 'toctree', 'todo', 'todolist', 'versionadded',
'versionchanged', 'c:function', 'coroutinefunction'
]

DIRECTIVES_CONTAINING_ARBITRARY_CONTENT = [
# standard docutils ones
'contents', 'csv-table', 'date', 'default-role', 'include', 'raw',
'restructuredtext-test-directive','role', 'rubric', 'sectnum', 'table',
'target-notes', 'title', 'unicode',
# Sphinx and Python docs custom ones
'productionlist', 'code-block',
]

# fmt: on


ALL_DIRECTIVES = "(" + "|".join(DIRECTIVES) + ")"
DIRECTIVES_CONTAINING_ARBITRARY_CONTENT_RE = (
"(" + "|".join(DIRECTIVES_CONTAINING_ARBITRARY_CONTENT) + ")"
)
DIRECTIVES_CONTAINING_RST_RE = "(" + "|".join(DIRECTIVES_CONTAINING_RST) + ")"
ALL_DIRECTIVES = (
"("
+ "|".join(DIRECTIVES_CONTAINING_RST + DIRECTIVES_CONTAINING_ARBITRARY_CONTENT)
+ ")"
)
BEFORE_ROLE = r"(^|(?<=[\s(/'{\[*-]))"
SIMPLENAME = r"(?:(?!_)\w)+(?:[-._+:](?:(?!_)\w)+)*"
ROLE_TAG = rf":{SIMPLENAME}:"
Expand Down Expand Up @@ -780,16 +796,18 @@ def check_leaked_markup(file, lines, options=None):

def is_multiline_non_rst_block(line):
"""Returns True if the next lines are an indented literal block."""
if line.endswith("..\n"):
return True
if line.endswith("::\n"):
if re.match(r"^\s*\.\.$", line): # it's the start of a comment block.
return True
if re.match(r"^ *\.\. code-block::", line):
if re.match(rf"^ *\.\. {DIRECTIVES_CONTAINING_RST_RE}::", line):
return False
if re.match(rf"^ *\.\. {DIRECTIVES_CONTAINING_ARBITRARY_CONTENT_RE}::", line):
return True
if re.match(r"^ *.. productionlist::", line):
return True
if re.match(r"^ *\.\. ", line) and type_of_explicit_markup(line) == "comment":
return True
if line.endswith("::\n"): # It's a literal block
return True
return False


Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/xfail/error-hidden-in-note.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.. expect: default role used
.. note::
This is a note, but it's still parsed as rst, so errors should be spotted.
Like using a `default role`.
2 changes: 2 additions & 0 deletions tests/fixtures/xpass/multiple-inline-literals.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``_PyUnicode_Name_CAPI`` de l'API PyCapsule ``unicodedata.ucnhash_CAPI``
a été déplacée dans l'API C interne. Contribution par Victor Stinner.
77 changes: 77 additions & 0 deletions tests/test_filter_out_literal.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,80 @@ def test_consecutive_production_list():
for line in hide_non_rst_blocks(CONSECUTIVE_PRODUCTION_LIST.splitlines(True)):
out.append(line)
assert "".join(out) == CONSECUTIVE_PRODUCTION_LIST_EXPECTED


ATTENTION = """
This is a test for an attention admonition.
.. attention::
An admonition can contain RST so it should **NOT** be dropped.
and that's it.
"""


def test_filter_out_attention():
out = []
excluded = []
for line in hide_non_rst_blocks(
ATTENTION.splitlines(True),
hidden_block_cb=lambda lineno, block: excluded.append((lineno, block)),
):
out.append(line)
assert "".join(out) == ATTENTION
assert not excluded


NOTE = """
This is a note, it contains rst, so it should **not** be dropped:
.. note::
hello I am a not **I can** contain rst.
End of it.
"""


def test_filter_out_note():
out = []
excluded = []
for line in hide_non_rst_blocks(
NOTE.splitlines(True),
hidden_block_cb=lambda lineno, block: excluded.append((lineno, block)),
):
out.append(line)
assert "".join(out) == NOTE
assert not excluded


UNKNOWN = """
This is an unknown directive, to avoid false positives, just drop its content.
.. this_is_not_a_known_directive::
So this can contain rst, or arbitary text.
In the face of ambiguity, refuse the temptation to guess.
"""

UNKNOWN_EXPECTED = """
This is an unknown directive, to avoid false positives, just drop its content.
In the face of ambiguity, refuse the temptation to guess.
"""


def test_filter_out_unknown():
out = []
excluded = []
for line in hide_non_rst_blocks(
UNKNOWN.splitlines(True),
hidden_block_cb=lambda lineno, block: excluded.append((lineno, block)),
):
out.append(line)
assert "".join(out) == UNKNOWN_EXPECTED

0 comments on commit a419948

Please sign in to comment.