Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make noqa parsing consistent and more robust #16483

Merged
merged 38 commits into from
Mar 11, 2025

Conversation

dylwil3
Copy link
Collaborator

@dylwil3 dylwil3 commented Mar 3, 2025

Summary

The goal of this PR is to address various issues around parsing suppression comments by

  1. Unifying the logic used to parse in-line (# noqa) and file-level (# ruff: noqa) noqa comments
  2. Recovering from certain errors and surfacing warnings in these cases

Closes #15682
Supersedes #12811
Addresses #14229 (comment)
Related: #14229 , #12809


Current suppression behavior

The implementations for parsing in-line and file-level noqa comments are entirely separate, so a number of discrepancies have arisen. You can unfurl some examples of this below.

Examples of discrepancies

Leading Hashes

This works:

from typing import List ## noqa: UP035

This does not, and emits no warning:

from typing import List
## ruff: noqa: UP035

Missing colon

This silently ignores all rules on the line, even though
the user intended a single rule to be ignored:

from typing import List # noqa UP035

This prints a warning and does not suppress anything:

from typing import List 
# ruff: noqa UP035

Missing Delimiter

This works to suppress both violations:

from typing import List # noqa: UP035F401

This warns and suppresses neither:

from typing import List 
#ruff: noqa: UP035F401

Multiple Commas

This works to suppress both violations:

from typing import List #noqa: UP035,,,F401

This suppresses the first but not the second.
It does not emit a warning:

from typing import List 
#ruff:noqa: UP035,,,F401

Missing whitespace before additional comments

This works:

from typing import List #noqa: UP035abc

This warns and does nothing:

from typing import List 
#ruff:noqa: UP035abc

Additional comments after noqa

This works:

from typing import List #noqa: UP035 and more comments

And so does this:

from typing import List #noqa and more comments

And so does this:

from typing import List 
#ruff: noqa: UP035 and more comments

But this emits a warning and suppresses nothing:

from typing import List 
#ruff:noqa and more comments

Design

The specification for the unified parsing logic is as follows:

  1. A suppression comment must be contained in a comment range and begins with a prefix matching one of the following regexes:
  • (file level) #\s*(?:ruff|flake8)\s*:\s*(?i)noqa
  • (inline) #\s*(?i)noqa
  1. If the character following the noqa is '#', whitespace, or if there are no characters following, then the suppression comment suppressed all rules. If the character following is ":", proceed to (3). Otherwise, warn the user for InvalidSuffix.
  2. We expect a list of rule codes, separated by commas or whitespace. After splitting first on commas, and then on whitespace, we attempt to parse each item in turn. If parsing a nonempty item returns no rule codes, we stop. If no codes were found at all we warn the user with MissingCodes.
  3. A rule code is a match for the regex [A-Z]+[0-9]+. While it is expected that there is one rule code per item, parsing continues even if that is not true, (e.g., F401F841 or F401,,F841), and the user is warned of a MissingDelimiter or MissingItem. If an item does not consist entirely of codes (e.g. F401abc), parsing aborts with an error and the user is warned of an InvalidCodeSuffix - the exception is if we encounter #, in which case parsing stops and returns all codes collected to that point (e.g. # noqa: F401#comment).

Implementation

After trimming the prefix for a file-level or inline noqa, the remaining text is passed to a lexer in the style of SimpleTokenizer. There is some small logic to "recover" in the case of missing delimiters or missing items.

Other changes and affected rules/behavior

The only rules that appear to be affected are blanket-noqa (PGH004)) and unused-noqa (RUF100) (but we'll see if others come up in the ecosystem check). For example, we no longer parse the first code in # noqa F401.F841 and instead emit a warning, which changes one of the snapshots for RUF100.

Internally, we no longer distinguish between a Directive and a ParsedFileExemption (keeping just the former), since these were used almost identically. They are carried around inside of NoqaDirectiveLine and FileNoqaDirectedLine, respectively, so there is little risk in misunderstanding the context if we just use Directive in both cases.

Review

After responding to comments it may no longer be helpful to proceed commit by commit. However, to see changes in behavior it may be best to view the diff against the first commit rather than main, since it contains a few additional snapshots.

Miscellaneous comments

@dylwil3 dylwil3 added breaking Breaking API change suppression Related to supression of violations e.g. noqa do-not-merge Do not merge this pull request labels Mar 3, 2025
@dylwil3 dylwil3 added this to the v0.10 milestone Mar 3, 2025
Copy link

codspeed-hq bot commented Mar 3, 2025

CodSpeed Performance Report

Merging #16483 will not alter performance

Comparing dylwil3:noqa-parsing (ae39f61) with micha/ruff-0.10 (41cd905)

Summary

✅ 32 untouched benchmarks

@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 3, 2025

Oh jeez! Well that performance regression is no good. I'd still welcome thoughts on the "design spec" and I'll work on improving the implementation in the meantime. Converting to draft for now...

@dylwil3 dylwil3 marked this pull request as draft March 3, 2025 21:41
Copy link
Contributor

github-actions bot commented Mar 3, 2025

ruff-ecosystem results

Linter (stable)

ℹ️ ecosystem check detected linter changes. (+0 -66 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)

qdrant/qdrant-client (+0 -66 violations, +0 -0 fixes)

- qdrant_client/http/api/aliases_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/aliases_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/aliases_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/beta_api.py:51:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/beta_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/beta_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/collections_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/collections_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/collections_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/distributed_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/distributed_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/distributed_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/indexes_api.py:126:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:144:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:162:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:180:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:59:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/indexes_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/indexes_api.py:94:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:158:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:192:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:226:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:356:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:424:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:458:19: F405 `WriteOrdering` may be undefined, or defined from star imports
... 32 additional changes omitted for rule F405
- qdrant_client/http/api/points_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/points_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/search_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/search_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/service_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/service_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/snapshots_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/snapshots_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
... 31 additional changes omitted for project

Changes by rule (3 rules affected)

code total + violation - violation + fix - fix
F405 48 0 48 0 0
F811 9 0 9 0 0
F403 9 0 9 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+0 -66 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)

qdrant/qdrant-client (+0 -66 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview

- qdrant_client/http/api/aliases_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/aliases_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/aliases_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/beta_api.py:51:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/beta_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/beta_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/collections_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/collections_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/collections_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/distributed_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/distributed_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/distributed_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/indexes_api.py:126:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:144:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:162:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:180:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:52:54: F405 `AsyncApiClient` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:59:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/indexes_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/indexes_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/indexes_api.py:94:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:158:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:192:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:226:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:356:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:424:19: F405 `WriteOrdering` may be undefined, or defined from star imports
- qdrant_client/http/api/points_api.py:458:19: F405 `WriteOrdering` may be undefined, or defined from star imports
... 32 additional changes omitted for rule F405
- qdrant_client/http/api/points_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/points_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/search_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/search_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/service_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/service_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
- qdrant_client/http/api/snapshots_api.py:5:27: F811 Redefinition of unused `BaseModel` from line 4
- qdrant_client/http/api/snapshots_api.py:7:1: F403 `from qdrant_client.http.models import *` used; unable to detect undefined names
... 31 additional changes omitted for project

Changes by rule (3 rules affected)

code total + violation - violation + fix - fix
F405 48 0 48 0 0
F811 9 0 9 0 0
F403 9 0 9 0 0

@InSyncWithFoo
Copy link
Contributor

InSyncWithFoo commented Mar 3, 2025

@dylwil3 \r is actually not allowed in a comment, since it signifies the end of that physical line (same as \n and \r\n) and thus also the end of the comment.

Additionally, the original implementation uses char.is_whitespace(), which allows non-ASCII whitespace characters as well. In #12809, I argued that only tabs and spaces should be allowed (and I would do so again), but Charlie thought Ruff should be more permissive in that regard.

@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 3, 2025

@dylwil3 \r is actually not allowed in a comment, since it signifies the end of that physical line (same as \n and \r\n) and thus also the end of the comment.

Additionally, the original implementation uses char.is_whitespace(), which allows non-ASCII whitespace characters as well. In #12809, I argued that only tabs and spaces should be allowed (and I would do so again), but Charlie thought Ruff should be more permissive in that regard.

Yeah, I tried removing disallowed whitespace as well and I don't think it makes a difference. I think the regex is too heavy to compete with the manual approach for this simple of a pattern.

I sort of agree regarding ascii whitespace, and I do like that the regex is a little more self documenting... I'm torn.

I'll try re-implementing the manual approach tomorrow and see if it's worth it - I can always mention the regex we're implementing in the doc-comment even if it's not used directly.

@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 4, 2025

Okay, performance fixed by reverting to manually implementing regex (now at least the file exemption and in-line cases are handled in visibly the same way).

I think the ecosystem check is actually correct, and is one of the changes in behavior consistent with the design: We are now allowing comments after a file-level exemption like: # ruff: noqa This is a bad file don't lint it. Therefore, in the same way that # noqa F401 is a probable typo that suppresses all rules, so too does # ruff: noqa F401 suppress all rules. This mistake is caught by blanket-noqa (PGH004) (in preview).

As an aside: It'd be nice if there were a not-too-complex way to alert users to the footgun # noqa F401 without them turning on PGH004 (since it's not a default rule). Maybe RUF100 could handle this (not a default but I think somewhat more popular)?

@dylwil3 dylwil3 marked this pull request as ready for review March 4, 2025 14:05
@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 7, 2025

Looks like ecosystem check revealed a few small issues - again reverting to draft briefly

Update: Fixed!

@dylwil3 dylwil3 marked this pull request as draft March 7, 2025 01:05
@dylwil3 dylwil3 marked this pull request as ready for review March 7, 2025 01:54
@MichaReiser
Copy link
Member

Hmm, I don't understand the ecosystem results. Are we now ignoring the code for file level suppressions?

@MichaReiser
Copy link
Member

For reference, the related PRs in Red Knot are #15046, #15078, and #15081

@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 7, 2025

Hmm, I don't understand the ecosystem results. Are we now ignoring the code for file level suppressions?

There is a missing colon, it should be

- # flake8: noqa E501
+ # flake8: noqa: E501

See the comment above #16483 (comment)

Note this is now consistent with what we have always done for inline noqa comments, i.e. # noqa E501 suppresses all rules on the line while # noqa: E501 just suppresses E501.

We could instead allow this "syntax error" and merge in the logic used in the rule blanket-noqa (PGH004), but that seemed like a larger change.

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this.

I've some smaller nit comments that I think will improve readability (and fewer offset calculations!).

My main design question are:

  • I find it hard to reason about the 3 different lexing methods and how they interact together. I wonder if we should have a single NoqaLexer that does the lexing for the entire comment instead. It may have helper methods similar to lex_code or lex_codes to split the logic but we avoid constructing multiple Cursors.
  • whether it would make sense to parse all suppression comments eagerly and collect them into a Vec<SuppressionComment> similar to what we do in Red Knot.

@@ -263,13 +123,23 @@ pub(crate) fn rule_is_ignored(
code: Rule,
offset: TextSize,
noqa_line_for: &NoqaMapping,
comment_ranges: &CommentRanges,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware that Ruff sometimes tests if a rule is ignored at a specific location similar to how it is done in Red Knot. I assumed it only filters out diagnostics at the very end.

I don't know if this would be a large change but it seems we now have multiple places where we lex noqa comments. Would it make more sense to analyze all comments once and extract all suppression comments (see linked Red Knot issues). Looking up if a rule is ignored would then be simplified to searching for a suppression comment for that line (or range), which should require fewer binary search steps than searching all comments. Emitting errors should then be as simple as iterating over all suppression comments and emitting errors for the incorrect once.

continue;

for warning in warnings {
warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards moving the message into LexicalWarning. It's otherwise very easy to forget changing the message here when adding a new variant.

@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 7, 2025

Thanks Micha, for the detailed review and correcting my rookie Cursor usage 😄 !

I agree that incorporating the prefix stripping into the lexer and keeping everything going from left to right would be easier to reason about - I hadn't done this because I thought there was some performance reason for the approach that was already present in the code. But I'll give it a try!

I like the idea of parsing the suppression comments eagerly, but I wonder if that should be a follow-up PR. I would have to think about when we parse them and where we store them; I'll have to check out what you've set up in Red Knot and see if the same thing works here.

@MichaReiser
Copy link
Member

I like the idea of parsing the suppression comments eagerly, but I wonder if that should be a follow-up PR. I would have to think about when we parse them and where we store them; I'll have to check out what you've set up in Red Knot and see if the same thing works here.

I'm okay with that. Also considering that this should go into 0.10

@MichaReiser
Copy link
Member

MichaReiser commented Mar 10, 2025

For the changelog: Could you write a short summary detailing which comments are no longer accepted that previously were and how the difference will manifest for users?

@MichaReiser MichaReiser changed the base branch from main to micha/ruff-0.10 March 10, 2025 14:25
@MichaReiser MichaReiser removed the do-not-merge Do not merge this pull request label Mar 10, 2025
@MichaReiser
Copy link
Member

Same here. Feel free to merge this PR into the ruff-0.10 feature branch once it's ready

@dylwil3
Copy link
Collaborator Author

dylwil3 commented Mar 10, 2025

For the changelog (let me know if you want me to actually put it in, say, BREAKING_CHANGES.md as part of this PR @MichaReiser):

  • Suppression comments

    The syntax rules governing inline (# noqa) and file-level (# ruff: noqa) suppression comments have been unified and overall made more flexible.

    In the following situations, a comment will suppress more rules than it used to:

    • Leading comments are now allowed for file-level suppression comments, e.g. # Some context # ruff: noqa. Previously, this noqa was silently ignored.
    • Trailing comments are now allowed for file-level suppression comments, e.g. # ruff: noqa This file is rough! and are both allowed, whereas previously this resulted in an error and the suppression did not take effect.
    • To support the previous change, mistaken syntax like ruff: noqa UP035 will now suppress all rules (it is missing a colon after noqa). This is consistent with the already established in-line behavior, and is guarded against by the preview behavior of PGH004.
    • Missing items in lists of rules are now read with a warning, e.g. #noqa: F401,,F841 will suppress F401 and F841 and emit MissingItem warning.
    • Skipped delimiters in lists of rules are now read in both the in-line and file-level comments and a warning is emitted, e.g. # ruff: noqa: F401F841 will suppress both F401 and F841 and a MissingDelimiter warning is emitted.

    In the following situations, a comment may suppress fewer rules than it used to:

    • It is now disallowed to have an invalid rule suffix in inline suppression comments, e.g. # noqa: UP035abc. This will log an error to the user and suppress no rules. This matches the current behavior for file-level suppression comments.
    • Whitespace is allowed between the colon and rule list, e.g. #noqa : F401 previously suppressed all rules, and will now only suppress F401. This used to be guarded against through PGH004, but that is now no longer needed.

    The full specification is as follows:

    • An in-line blanket noqa comment is given by a case-insensitive match for #noqa with optional whitespace after the # symbol, followed by either: the end of the comment, the beginning of a new comment ('#'), or whitespace followed by any character other than ':'.
    • An in-line rule suppression is given by first finding a case-insensitive match for #noqa with optional whitespace after the # symbol, optional whitespace after noqa, and followed by the symbol ':'. After this we are expected to have a list of rule codes which is given by sequences of uppercase ascii characters followed by ascii digits, separated by whitespace or commas. The list ends at the last valid code. We will attempt to interpret rules with a missing delimiter (e.g. F401F841), though a warning will be emitted in this case.
    • A file-level exemption comment is given by a case-sensitive match for #ruff: or #flake8:, with optional whitespace after '#' and before ':', followed by optional whitespace and a case-insensitive match for noqa. After this, the specification is as in the in-line case.

@dylwil3 dylwil3 merged commit b8f1284 into astral-sh:micha/ruff-0.10 Mar 11, 2025
21 checks passed
@dylwil3 dylwil3 mentioned this pull request Mar 11, 2025
2 tasks
MichaReiser pushed a commit that referenced this pull request Mar 13, 2025
# Summary
The goal of this PR is to address various issues around parsing
suppression comments by

1. Unifying the logic used to parse in-line (`# noqa`) and file-level
(`# ruff: noqa`) noqa comments
2. Recovering from certain errors and surfacing warnings in these cases

Closes #15682 
Supersedes #12811 
Addresses
#14229 (comment)
Related: #14229 , #12809
MichaReiser pushed a commit that referenced this pull request Mar 13, 2025
# Summary
The goal of this PR is to address various issues around parsing
suppression comments by

1. Unifying the logic used to parse in-line (`# noqa`) and file-level
(`# ruff: noqa`) noqa comments
2. Recovering from certain errors and surfacing warnings in these cases

Closes #15682 
Supersedes #12811 
Addresses
#14229 (comment)
Related: #14229 , #12809
ntBre added a commit that referenced this pull request Mar 13, 2025
## Summary

Adds @dylwil3's new `noqa` specification to the linter `Error
suppression` page instead of the release blog post. Originally taken
from his PR comment
[here](#16483 (comment)).

## Test Plan

None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Breaking API change suppression Related to supression of violations e.g. noqa
Projects
None yet
Development

Successfully merging this pull request may close these issues.

unused-noqa (RUF100) - false negatives and strange behavior with multiple codes
4 participants