Skip to content

Commit f2dae25

Browse files
SAY-5DimitriPapadopoulos
authored andcommitted
feat: support codespell:ignore-next-line directive
1 parent f22c325 commit f2dae25

3 files changed

Lines changed: 89 additions & 2 deletions

File tree

README.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,22 @@ Words should be separated by a comma.
156156
def wrod(wrods) # codespell:ignore
157157
pass
158158
159+
3. ignore the following line (useful when a formatter pushes comments to a new line):
160+
161+
.. code-block:: python
162+
163+
# codespell:ignore-next-line wrod
164+
def wrod():
165+
pass
166+
167+
Use the bare form to ignore every misspelling on the next line:
168+
169+
.. code-block:: python
170+
171+
# codespell:ignore-next-line
172+
def wrod(wrods):
173+
pass
174+
159175
Using a config file
160176
-------------------
161177

codespell_lib/_codespell.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@
5656
r"(\b(?:https?|[ts]?ftp|file|git|smb)://[^\s]+(?=$|\s)|\b[\w.%+-]+@[\w.-]+\b)"
5757
)
5858
codespell_ignore_tag = "codespell:ignore"
59+
codespell_ignore_next_line_tag = "codespell:ignore-next-line"
5960
inline_ignore_regex = re.compile(
60-
rf"[^\w\s]\s*{codespell_ignore_tag}\b(\s+(?P<words>[\w,]*))?"
61+
rf"[^\w\s]\s*{codespell_ignore_tag}(?!-)\b(\s+(?P<words>[\w,]*))?"
62+
)
63+
ignore_next_line_regex = re.compile(
64+
rf"[^\w\s]\s*{codespell_ignore_next_line_tag}\b(\s+(?P<words>[\w,]*))?"
6165
)
6266
USAGE = """
6367
\t%prog [OPTIONS] [file1 file2 ... fileN]
@@ -967,13 +971,28 @@ def parse_lines(
967971

968972
_, fragment_line_number, lines = fragment
969973

974+
next_line_ignore_words: Optional[set[str]] = None
975+
970976
for i, line in enumerate(lines):
971977
line = line.rstrip()
978+
# Apply any ignore-next-line directive carried from the previous line.
979+
pending_next_line_ignore = next_line_ignore_words
980+
next_line_ignore_words = None
981+
982+
directive_words: set[str] = set()
983+
if codespell_ignore_next_line_tag in line:
984+
nl_match = ignore_next_line_regex.search(line)
985+
if nl_match:
986+
directive_words = set(
987+
filter(None, (nl_match.group("words") or "").split(","))
988+
)
989+
next_line_ignore_words = directive_words
990+
972991
if not line or line in exclude_lines:
973992
continue
974993
line_number = fragment_line_number + i
975994

976-
extra_words_to_ignore = set()
995+
extra_words_to_ignore: set[str] = set()
977996
match = (
978997
inline_ignore_regex.search(line) if codespell_ignore_tag in line else None
979998
)
@@ -984,6 +1003,14 @@ def parse_lines(
9841003
if not extra_words_to_ignore:
9851004
continue
9861005

1006+
# Words named in an ignore-next-line directive are ignored on its own line too.
1007+
extra_words_to_ignore |= directive_words
1008+
1009+
if pending_next_line_ignore is not None:
1010+
if not pending_next_line_ignore:
1011+
continue
1012+
extra_words_to_ignore |= pending_next_line_ignore
1013+
9871014
fixed_words = set()
9881015
asked_for = set()
9891016

codespell_lib/tests/test_basic.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,50 @@ def test_inline_ignores(
493493
assert cs.main(d) == expected_error_count
494494

495495

496+
@pytest.mark.parametrize(
497+
("content", "expected_error_count"),
498+
[
499+
# wildcard form: ignore all misspellings on the next line
500+
("# codespell:ignore-next-line\nabandonned abondon abilty\n", 0),
501+
("// codespell:ignore-next-line\nabandonned abondon abilty\n", 0),
502+
# specific word form: ignore only listed words on the next line
503+
(
504+
"# codespell:ignore-next-line abondon\nabandonned abondon abilty\n",
505+
2,
506+
),
507+
(
508+
"# codespell:ignore-next-line abondon,abilty\nabandonned abondon abilty\n",
509+
1,
510+
),
511+
# the directive does not affect the line it is on or subsequent lines
512+
(
513+
"abandonned # codespell:ignore-next-line\nabondon\nabilty\n",
514+
2,
515+
),
516+
# listing an unused ignore word still triggers a skip
517+
(
518+
"# codespell:ignore-next-line nomenklatur\nabandonned abondon abilty\n",
519+
3,
520+
),
521+
# invalid directives are not honored
522+
("# codespell:ignore-next-lin\nabandonned\n", 1),
523+
("codespell:ignore-next-line\nabandonned\n", 1),
524+
# directive followed by a blank line still consumes the directive
525+
("# codespell:ignore-next-line\n\nabandonned\n", 1),
526+
],
527+
)
528+
def test_ignore_next_line(
529+
tmpdir: pytest.TempPathFactory,
530+
capsys: pytest.CaptureFixture[str],
531+
content: str,
532+
expected_error_count: int,
533+
) -> None:
534+
d = str(tmpdir)
535+
with open(op.join(d, "bad.txt"), "w", encoding="utf-8") as f:
536+
f.write(content)
537+
assert cs.main(d) == expected_error_count
538+
539+
496540
def test_custom_regex(
497541
tmp_path: Path,
498542
capsys: pytest.CaptureFixture[str],

0 commit comments

Comments
 (0)