Skip to content

Clarify escaping-backslashes at end of inline markup. #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ A quick way to test if some syntax is valid from a pure
reStructuredText point of view, one case use `docutils`'s `pseudoxml`
writer, like:

```text
$ docutils --writer=pseudoxml <(echo '``hello``')
<document source="/dev/fd/63">
<paragraph>
<literal>
hello
```

Or against a whole file:

```text
$ docutils --writer=pseudoxml tests/fixtures/xpass/role-in-code-sample.rst
<document source="tests/fixtures/xpass/role-in-code-sample.rst">
Expand Down
9 changes: 5 additions & 4 deletions sphinxlint/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,12 @@ def check_missing_space_after_role(file, lines, options=None):
Bad: :exc:`Exception`s.
Good: :exc:`Exceptions`\ s
"""
for lno, line in enumerate(lines, start=1):
line = clean_paragraph(line)
role = _SUSPICIOUS_ROLE.search(line)
for paragraph_lno, paragraph in paragraphs(lines):
paragraph = clean_paragraph(paragraph)
role = _SUSPICIOUS_ROLE.search(paragraph)
if role:
yield lno, f"role missing (escaped) space after role: {role.group(0)!r}"
error_offset = paragraph[: role.start()].count("\n")
yield paragraph_lno + error_offset, f"role missing (escaped) space after role: {role.group(0)!r}"


@checker(".rst", ".po")
Expand Down
9 changes: 6 additions & 3 deletions sphinxlint/rst.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,14 @@ def inline_markup_gen(start_string, end_string, extra_allowed_before=""):
"""
if extra_allowed_before:
extra_allowed_before = "|" + extra_allowed_before
# Both, inline markup start-string and end-string must not be
# preceded by an unescaped backslash (except for the end-string of
# inline literals).
if not (start_string == "``" and end_string == "``"):
end_string = f"(?<!\x00){end_string}"
start_string = f"(?<!\x00){start_string}"
return re.compile(
rf"""
(?<!\x00) # Both inline markup start-string and end-string must not be preceded by
# an unescaped backslash

(?<= # Inline markup start-strings must:
^| # start a text block
\s| # or be immediately preceded by whitespace,
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/xpass/ref-in-samp.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:samp:`"Please refer to the :ref:\`{security-considerations}\`
section."`
28 changes: 28 additions & 0 deletions tests/test_rst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from sphinxlint.utils import escape2null as e2n
from sphinxlint.rst import inline_markup_gen


def test_inline_literals_can_end_with_escaping_backslash():
"""See https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html
paragraph: Inline markup recognition rules
"""
inline_markup_re = inline_markup_gen("``", "``")
assert inline_markup_re.findall(e2n(r"``\``")) == [e2n(r"``\``")]
assert inline_markup_re.findall(e2n(r"``\\``")) == [e2n(r"``\\``")]
assert inline_markup_re.findall(e2n(r"``\\\``")) == [e2n(r"``\\\``")]
assert inline_markup_re.findall(e2n(r"``\\\\``")) == [e2n(r"``\\\\``")]


def test_emphasis_cannot_end_with_escaping_backslash():
"""See https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html
paragraph: Inline markup recognition rules
"""
emphasis_re = inline_markup_gen(r"\*", r"\*")
assert emphasis_re.findall(e2n(r"*\*")) == []
assert emphasis_re.findall(e2n(r"*\\*")) == [e2n(r"*\\*")]
assert emphasis_re.findall(e2n(r"*\\\*")) == []
assert emphasis_re.findall(e2n(r"*\\\\*")) == [e2n(r"*\\\\*")]

assert emphasis_re.findall(
e2n(r"Tous les *\*args* et *\*\*kwargs* fournis à cette fonction sont")
) == [e2n(r"*\*args*"), e2n(r"*\*\*kwargs*")]