From 5cd1f2d68524254fd0f664f5d93684e7f56edd91 Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Fri, 14 Aug 2026 17:12:06 +0300 Subject: [PATCH 1/2] Do not report a fix that --ignore-regex made impossible Detection runs on the text with --ignore-regex substituted out, while --write-changes substitutes on the original line. A word that only exists in the substituted text (1nd in 1nd_2nd, with --ignore-regex _) therefore matches nothing: the line is left untouched, but codespell still prints FIXED, still lists the change, and still exits 0. Keep the result of the substitution and only claim the fix when the line actually changed; otherwise fall through to the normal warning, which also restores the exit code. Fixes #2056 --- codespell_lib/_codespell.py | 16 +++++++++++----- codespell_lib/tests/test_basic.py | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index 994c5c4a66..299eba8006 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -1093,11 +1093,17 @@ def parse_lines( continue if options.write_changes and fix: - changed = True - lines[i] = re.sub(rf"\b{word}\b", fixword, lines[i]) - fixed_words.add(word) - changes_made.append((line_number + 1, word, fixword)) - continue + new_line = re.sub(rf"\b{word}\b", fixword, lines[i]) + if new_line != lines[i]: + changed = True + lines[i] = new_line + fixed_words.add(word) + changes_made.append((line_number + 1, word, fixword)) + continue + # The word was found in the --ignore-regex substituted text + # but cannot be located in the original line, so no fix can + # be written. Fall through and report it as a warning + # instead of claiming a fix that did not happen. # otherwise warning was explicitly set by interactive mode if ( diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index 930f09f14a..c9bb940183 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -1054,6 +1054,33 @@ def test_ignore_regex_option( assert cs.main(fname, r"--ignore-regex=\bdonn\b") == 1 +def test_ignore_regex_with_write_changes( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test that -w never claims a fix it did not perform (gh-2056).""" + fname = tmp_path / "flag.txt" + content = "1nd\n1nd_2nd\n" + + # Without -w, --ignore-regex=_ makes BOTH lines report a misspelling. + fname.write_text(content) + assert cs.main(fname, "--ignore-regex=_") == 2 + + fname.write_text(content) + result = cs.main("-w", "--ignore-regex=_", fname, std=True) + assert isinstance(result, tuple) + code, _, stderr = result + corrected = fname.read_text() + + # Line 1 really is rewritten. + assert corrected == "1st\n1nd_2nd\n" + # Line 2 cannot be rewritten (\b1nd\b does not match "1nd_2nd"), + # so codespell must not claim it was fixed ... + assert "flag.txt:2: 1nd ==> 1st" not in stderr + # ... and the still-present misspelling must affect the exit code. + assert code != 0 + + def test_ignore_multiline_regex_option( tmp_path: Path, capsys: pytest.CaptureFixture[str], From 207e0170fe28f8ff7222376c9fd843f0ed341aad Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 17 Aug 2026 21:47:23 -0400 Subject: [PATCH 2/2] Tweaks --- codespell_lib/_codespell.py | 6 ++---- codespell_lib/tests/test_basic.py | 11 ++++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index 299eba8006..e7d94aa7e5 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -1100,10 +1100,8 @@ def parse_lines( fixed_words.add(word) changes_made.append((line_number + 1, word, fixword)) continue - # The word was found in the --ignore-regex substituted text - # but cannot be located in the original line, so no fix can - # be written. Fall through and report it as a warning - # instead of claiming a fix that did not happen. + # Not found in the original line (e.g. --ignore-regex split + # it out of a larger word), so report it instead (GH-2056). # otherwise warning was explicitly set by interactive mode if ( diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index c9bb940183..c82d8477dc 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -1069,16 +1069,17 @@ def test_ignore_regex_with_write_changes( fname.write_text(content) result = cs.main("-w", "--ignore-regex=_", fname, std=True) assert isinstance(result, tuple) - code, _, stderr = result + code, stdout, stderr = result corrected = fname.read_text() # Line 1 really is rewritten. assert corrected == "1st\n1nd_2nd\n" - # Line 2 cannot be rewritten (\b1nd\b does not match "1nd_2nd"), - # so codespell must not claim it was fixed ... + # Line 2 cannot be rewritten (\b1nd\b does not match "1nd_2nd"), so it must + # not be listed as fixed, must be reported as a misspelling instead ... assert "flag.txt:2: 1nd ==> 1st" not in stderr - # ... and the still-present misspelling must affect the exit code. - assert code != 0 + assert "flag.txt:2: 1nd ==> 1st" in stdout + # ... and must be counted, so that it affects the exit code. + assert code == 1 def test_ignore_multiline_regex_option(