Implement Invalid rule provided as rule RUF102 with --fix (#17138)

<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

Closes #17084

## Summary
This PR adds a new rule (RUF102) to detect and fix invalid rule codes in
`noqa` comments.
Invalid rule codes in `noqa` directives serve no purpose and may
indicate outdated code suppressions.

This extends the previous behaviour originating from
`crates/ruff_linter/src/noqa.rs` which would only emit a warnigs.
With this rule a `--fix` is available.

The rule:
1. Analyzes all `noqa` directives to identify invalid rule codes
2. Provides autofix functionality to:
   - Remove the entire comment if all codes are invalid
   - Remove only the invalid codes when mixed with valid codes
3. Preserves original comment formatting and whitespace where possible

Example cases:
- `# noqa: XYZ111` → Remove entire comment (keep empty line)
- `# noqa: XYZ222, XYZ333` → Remove entire comment (keep empty line)
-  `# noqa: F401, INVALID123` → Keep only valid codes (`# noqa: F401`)

## Test Plan
- Added tests in
`crates/ruff_linter/resources/test/fixtures/ruff/RUF102.py` covering
different example cases.

<!-- How was it tested? -->

## Notes 
- This does not handle cases where parsing fails. E.g. `# noqa:
NON_EXISTENT, ANOTHER_INVALID` causes a `LexicalError` and the
diagnostic is not propagated and we cannot handle the diagnostic. I am
also unsure what proper `fix` handling would be and making the user
aware we don't understand the codes is probably the best bet.
- The rule is added to the Preview rule group as it's a new addition

## Questions
- Should we remove the warnings, now that we have a rule?
- Is the current fix behavior appropriate for all cases, particularly
the handling of whitespace and line deletions?
- I'm new to the codebase; let me know if there are rule utilities which
could have used but didn't.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Max Mynter 2025-04-04 10:05:59 +02:00 committed by GitHub
parent a4ba10ff0a
commit 98b95c9c38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 605 additions and 0 deletions

View file

@ -0,0 +1,18 @@
# Invalid code
import os # noqa: INVALID123
# External code
import re # noqa: V123
# Valid noqa
import sys # noqa: E402
from functools import cache # Preceeding comment # noqa: F401, INVALID456
from itertools import product # Preceeding comment # noqa: INVALID789
# Succeeding comment
import math # noqa: INVALID000 # Succeeding comment
# Mixed valid and invalid
from typing import List # noqa: F401, INVALID123
# Test for multiple invalid
from collections import defaultdict # noqa: INVALID100, INVALID200, F401
# Test for preserving valid codes when fixing
from itertools import chain # noqa: E402, INVALID300, F401
# Test for mixed code types
import json # noqa: E402, INVALID400, V100