diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index 94ab65d068..341795b331 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -883,6 +883,32 @@ def apply_uri_ignore_words( return check_matches +def _format_colored_output( + filename: str, + colors: TermColors, + line_num: int, + wrong: str, + right: str, +) -> tuple[str, str, str, str]: + """Format colored strings for output. + + Args: + filename: The filename being processed. + colors: TermColors instance for color formatting. + line_num: Line number (1-based) where the misspelling was found. + wrong: The misspelled word. + right: The correct word. + + Returns: + Tuple of (filename, line_num, wrong_word, right_word) with color codes. + """ + cfilename = f"{colors.FILE}{filename}{colors.DISABLE}" + cline = f"{colors.FILE}{line_num}{colors.DISABLE}" + cwrongword = f"{colors.WWORD}{wrong}{colors.DISABLE}" + crightword = f"{colors.FWORD}{right}{colors.DISABLE}" + return cfilename, cline, cwrongword, crightword + + def parse_lines( fragment: tuple[bool, int, list[str]], filename: str, @@ -897,9 +923,10 @@ def parse_lines( uri_ignore_words: set[str], context: Optional[tuple[int, int]], options: argparse.Namespace, -) -> tuple[int, bool]: +) -> tuple[int, bool, list[tuple[int, str, str]]]: bad_count = 0 changed = False + changes_made: list[tuple[int, str, str]] = [] _, fragment_line_number, lines = fragment @@ -985,6 +1012,7 @@ def parse_lines( 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 # otherwise warning was explicitly set by interactive mode @@ -995,10 +1023,9 @@ def parse_lines( ): continue - cfilename = f"{colors.FILE}{filename}{colors.DISABLE}" - cline = f"{colors.FILE}{line_number + 1}{colors.DISABLE}" - cwrongword = f"{colors.WWORD}{word}{colors.DISABLE}" - crightword = f"{colors.FWORD}{fixword}{colors.DISABLE}" + cfilename, cline, cwrongword, crightword = _format_colored_output( + filename, colors, line_number + 1, word, fixword + ) reason = misspellings[lword].reason if reason: @@ -1028,7 +1055,7 @@ def parse_lines( f"==> {crightword}{creason}" ) - return bad_count, changed + return bad_count, changed, changes_made def parse_file( @@ -1068,9 +1095,9 @@ def parse_file( if summary and fix: summary.update(lword) - cfilename = f"{colors.FILE}{filename}{colors.DISABLE}" - cwrongword = f"{colors.WWORD}{word}{colors.DISABLE}" - crightword = f"{colors.FWORD}{fixword}{colors.DISABLE}" + cfilename, _, cwrongword, crightword = _format_colored_output( + filename, colors, 0, word, fixword + ) reason = misspellings[lword].reason if reason: @@ -1109,12 +1136,13 @@ def parse_file( # Parse lines. changed = False + changes_made: list[tuple[int, str, str]] = [] for fragment in fragments: ignore, _, _ = fragment if ignore: continue - bad_count_update, changed_update = parse_lines( + bad_count_update, changed_update, changes_made_update = parse_lines( fragment, filename, colors, @@ -1131,6 +1159,7 @@ def parse_file( ) bad_count += bad_count_update changed = changed or changed_update + changes_made.extend(changes_made_update) # Write out lines, if changed. if changed: @@ -1145,6 +1174,14 @@ def parse_file( f"{colors.FWORD}FIXED:{colors.DISABLE} {filename}", file=sys.stderr, ) + for line_num, wrong, right in changes_made: + cfilename, cline, cwrongword, crightword = _format_colored_output( + filename, colors, line_num, wrong, right + ) + print( + f" {cfilename}:{cline}: {cwrongword} ==> {crightword}", + file=sys.stderr, + ) with open(filename, "w", encoding=encoding, newline="") as f: for _, _, lines in fragments: f.writelines(lines) diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index 56a58942af..c8b078a738 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -169,6 +169,31 @@ def test_basic( assert cs.main(tmp_path) == 0 +def test_write_changes_lists_changes( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test that -w flag shows list of changes made to file.""" + + fname = tmp_path / "misspelled.txt" + fname.write_text("This is abandonned\nAnd this is occured\nAlso teh typo\n") + + result = cs.main("-w", fname, std=True) + assert isinstance(result, tuple) + code, _, stderr = result + assert code == 0 + + assert "FIXED:" in stderr + + # Check that changes are listed with format: filename:line: wrong ==> right + assert "misspelled.txt:1: abandonned ==> abandoned" in stderr + assert "misspelled.txt:2: occured ==> occurred" in stderr + assert "misspelled.txt:3: teh ==> the" in stderr + + corrected = fname.read_text() + assert corrected == "This is abandoned\nAnd this is occurred\nAlso the typo\n" + + def test_default_word_parsing( tmp_path: Path, capsys: pytest.CaptureFixture[str],