diff --git a/CHANGELOG.md b/CHANGELOG.md index ecbcc18..2927026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Added + +- Silence `fixit` errors with *inline* comments. + + `fixit` does not always respect `lint-fixme` comments when they are on the + line above the line causing the error. This is a known bug and is reported + in https://github.com/Instagram/Fixit/issues/405. + + In some of these cases (e.g. decorators), placing the comment on the same line + as the error can ensure it is respected. The `fixme-inline` linter option + allows the comments to be added inline instead of on the lien above. + + N.B. This might prevent the comments from being successfully removed by the + `fix fixit` command, if there are other errors ignored on the same line. + ## 1.1.0 (2023-11-24) ### Added diff --git a/silence_lint_error/silence_lint_error.py b/silence_lint_error/silence_lint_error.py index e23d95d..c2c4f14 100644 --- a/silence_lint_error/silence_lint_error.py +++ b/silence_lint_error/silence_lint_error.py @@ -114,6 +114,28 @@ def silence_violations( return ''.join(new_lines) +class FixitInline(Fixit): + """An alternative `fixit` implementation that adds `lint-fixme` comment inline. + + This is sometimes necessary because `fixit` does not always respect `lint-fixme` + comments when they are on the line above the line causing the error. This is a + known bug and is reported in https://github.com/Instagram/Fixit/issues/405. + + In some of these cases, placing the comment on the same line as the error can + ensure it is respected (e.g. for decorators). + """ + + def silence_violations( + self, src: str, violations: Sequence[Violation], + ) -> str: + [rule_name] = {violation.rule_name for violation in violations} + linenos_to_silence = {violation.lineno for violation in violations} + return comments.add_error_silencing_comments( + src, linenos_to_silence, + 'lint-fixme', rule_name, + ) + + class Flake8: name = 'flake8' @@ -193,6 +215,7 @@ def silence_violations( LINTERS: dict[str, type[Linter]] = { 'fixit': Fixit, + 'fixit-inline': FixitInline, 'flake8': Flake8, 'ruff': Ruff, } diff --git a/tests/silence_lint_error/fixit_test.py b/tests/silence_lint_error/fixit_test.py index 2e44b7e..08525f4 100644 --- a/tests/silence_lint_error/fixit_test.py +++ b/tests/silence_lint_error/fixit_test.py @@ -139,6 +139,42 @@ def f(x): """ +def test_main_inline(tmp_path: Path, capsys: pytest.CaptureFixture[str]): + python_module = tmp_path / 't.py' + python_module.write_text( + """\ +x = None +isinstance(x, str) or isinstance(x, int) + +def f(x): + return isinstance(x, str) or isinstance(x, int) +""", + ) + + ret = main( + ('fixit-inline', 'fixit.rules:CollapseIsinstanceChecks', str(python_module)), + ) + + assert ret == 1 + assert python_module.read_text() == """\ +x = None +isinstance(x, str) or isinstance(x, int) # lint-fixme: CollapseIsinstanceChecks + +def f(x): + return isinstance(x, str) or isinstance(x, int) # lint-fixme: CollapseIsinstanceChecks +""" # noqa: B950 + + captured = capsys.readouterr() + assert captured.out == f"""\ +{python_module} +""" + assert captured.err == """\ +-> finding errors with fixit +found errors in 1 files +-> adding comments to silence errors +""" + + def test_main_no_violations( tmp_path: Path, capsys: pytest.CaptureFixture[str], ):