Skip to content

[ruff] Add rule to replace noqa comments with ruff:ignore (RUF105) - #26423

Merged
ntBre merged 19 commits into
mainfrom
brent/noqa-comment
Jul 9, 2026
Merged

[ruff] Add rule to replace noqa comments with ruff:ignore (RUF105)#26423
ntBre merged 19 commits into
mainfrom
brent/noqa-comment

Conversation

@ntBre

@ntBre ntBre commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

This is the first in a series of migration rules to help users move from noqa comments with rule
codes to ruff:ignore comments with names. This rule simply replaces noqa: codes with
ruff:ignore[codes] (and the file-level variants), deferring the transformation of codes to names
to a follow-up rule. This rule may be useful on its own without the codes -> names rule, assuming we
stabilize ruff:ignore in the next minor release without stabilizing human-readable names.

The rule skips any comment consisting entirely of known external selectors, while still emitting diagnostics for totally unknown codes. We also skip file-level flake8: noqa directives assuming they are intended to be shared with flake8.

I initially wrote a small, standalone implementation in the rule file itself, but it had to be
combined with the RUF100 implementation to leave unused noqa codes out of RUF105 to be cleaned
up by RUF100 instead, as I note in the mdtest for this case.

Test Plan

New mdtests

I also expect a huge number of ecosystem results on this PR, possibly so many that the check times out.

@ntBre ntBre added rule Implementing or modifying a lint rule preview Related to preview mode features labels Jun 26, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

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

DisnakeDev/disnake (+64 -0 violations, +0 -0 fixes)

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

+ disnake/app_commands.py:477:33: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/asset.py:108:40: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/components.py:131:97: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/components.py:169:102: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/entitlement.py:157:63: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/ext/commands/bot_base.py:146:38: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/ext/commands/flag_converter.py:423:44: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/ext/commands/params.py:127:39: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/ext/commands/params.py:392:36: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ disnake/ext/commands/params.py:547:84: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 54 additional changes omitted for project

RasaHQ/rasa (+73 -0 violations, +0 -0 fixes)

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

+ .github/tests/test_download_pretrained.py:102:19: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/tests/test_download_pretrained.py:26:19: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/tests/test_download_pretrained.py:46:19: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/tests/test_download_pretrained.py:63:19: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/tests/test_download_pretrained.py:83:19: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/tests/test_model_regression_test_results_tmpl.py:27:6: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/tests/test_model_regression_test_results_tmpl.py:49:6: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ rasa/__init__.py:3:35: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ rasa/__init__.py:4:40: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ rasa/core/channels/__init__.py:3:43: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 63 additional changes omitted for project

apache/airflow (+768 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ airflow-core/src/airflow/api_fastapi/execution_api/deps.py:19:1: noqa-comments [*] `ruff: noqa` comment used instead of `ruff:file-ignore`
+ airflow-core/src/airflow/api_fastapi/execution_api/security.py:68:1: noqa-comments [*] `ruff: noqa` comment used instead of `ruff:file-ignore`
+ airflow-core/src/airflow/callbacks/callback_requests.py:29:88: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/cli/commands/api_server_command.py:152:30: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/dag_processing/bundles/manager.py:31:64: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/executors/workloads/trigger.py:27:63: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/jobs/scheduler_job_runner.py:1489:143: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/jobs/scheduler_job_runner.py:1494:54: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/models/callback.py:33:81: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ airflow-core/src/airflow/models/dagrun.py:525:65: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 758 additional changes omitted for project

apache/superset (+2991 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ RELEASING/changelog.py:274:59: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/changelog.py:281:53: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/changelog.py:292:82: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/generate_email.py:34:111: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/verify_release.py:144:35: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/verify_release.py:26:131: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/verify_release.py:31:89: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/verify_release.py:46:93: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/verify_release.py:56:162: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ RELEASING/verify_release.py:78:31: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 2981 additional changes omitted for project

bokeh/bokeh (+18 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ examples/basic/data/color_mappers.py:9:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/interaction/js_callbacks/color_sliders.py:9:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/models/buttons.py:15:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/models/calendars.py:14:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/models/sliders.py:9:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/models/widgets.py:11:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/plotting/glyphs.py:8:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ examples/plotting/sprint.py:13:5: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/bokeh/core/property/pd.py:31:35: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/bokeh/core/property/pd.py:32:54: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 8 additional changes omitted for project

ibis-project/ibis (+320 -0 violations, +0 -0 fixes)

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

+ .github/workflows/algolia/configure-algolia-api.py:1:37: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia-api.py:164:54: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia-api.py:178:56: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia-api.py:190:66: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia-api.py:1:37: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia-api.py:203:53: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia-api.py:208:66: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ .github/workflows/algolia/upload-algolia.py:1:37: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ ci/make_geography_db.py:98:21: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ docs/backends/_utils.py:46:27: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 310 additional changes omitted for project

langchain-ai/langchain (+1106 -0 violations, +0 -0 fixes)

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

+ libs/core/langchain_core/_api/deprecation.py:296:74: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1082:14: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1114:32: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1141:32: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1157:32: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1170:32: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1200:32: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/base.py:1208:56: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/file.py:144:44: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ libs/core/langchain_core/callbacks/file.py:78:69: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 1096 additional changes omitted for project

latchbio/latch (+11 -0 violations, +0 -0 fixes)

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

+ src/latch/resources/launch_plan.py:53:15: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/constants.py:8:17: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/main.py:1:1: noqa-comments [*] `ruff: noqa` comment used instead of `ruff:file-ignore`
+ src/latch_cli/main.py:21:67: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/services/docker/utils.py:116:54: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/services/k8s/develop.py:7:20: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/services/launch/launch_v2.py:402:80: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/services/launch/launch_v2.py:464:59: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/services/login.py:66:28: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
+ src/latch_cli/services/private_images.py:195:54: noqa-comments [*] `noqa` comment used instead of `ruff:ignore`
... 1 additional changes omitted for project

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
noqa-comments 18503 18503 0 0 0

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

ntBre added 5 commits July 7, 2026 12:30
…105`)

Summary
--

This is the first in a series of migration rules to help users move from `noqa` comments with rule
codes to `ruff:ignore` comments with names. This rule simply replaces `noqa: codes` with
`ruff:ignore[codes]` (and the file-level variants), deferring the transformation of codes to names
to a follow-up rule. This rule may be useful on its own without the codes -> names rule, assuming we
stabilize `ruff:ignore` in the next minor release without stabilizing human-readable names.

The rule skips any comment with an unknown selector, so it doesn't have to inspect `lint.external`
like many of the other `noqa` rules. We also skip file-level `flake8: noqa` directives assuming they
are intended to be shared with flake8.

Codex pointed out one remaining issue with suppressing RUF105, but I think I'm convinced that this
is a non-issue. You can't suppress `RUF105` with a `ruff:ignore` comment because it only runs after
`Suppressions::check_suppressions`. This seems okay to me because it's pretty contradictory to try
to add `ruff:ignore[RUF105]` to prevent an existing `noqa` comment from being converted to a
`ruff:ignore` comment.

I initially wrote a small, standalone implementation in the rule file itself, but it had to be
combined with the `RUF100` implementation to leave unused `noqa` codes out of `RUF105` to be cleaned
up by `RUF100` instead.

Test Plan
--

New mdtests

I also expect a huge number of ecosystem results on this PR, possibly so many that the check times out.
@ntBre
ntBre force-pushed the brent/noqa-comment branch from 8ccf915 to 68a5ade Compare July 7, 2026 17:29
Comment thread python/ruff-ecosystem/ruff_ecosystem/check.py
@ntBre
ntBre marked this pull request as ready for review July 7, 2026 20:42
@ntBre
ntBre requested a review from MichaReiser July 7, 2026 20:42

@MichaReiser MichaReiser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Codex pointed out one remaining issue with suppressing RUF105, but I think I'm convinced that this
is a non-issue. Namely, you can't suppress RUF105 with a ruff:ignore comment because it only runs after
Suppressions::check_suppressions. This seems okay to me because it's pretty contradictory to try
to add ruff:ignore[RUF105] to prevent an existing noqa comment from being converted to a
ruff:ignore comment.

I'd find this surprising as a user, and the reason why I can't suppress this lint wouldn't be obvious to me. I don't know if it introduces much complexity, but it seems worth fixing.

I'd also assume that, in this case, --add-ignore doesn't work with RUF105 because it will add a ruff:ignore that is then simply ignored.

Codex also found two other issues:

  • RUF101 does not apply to ruff:ignore. This is not specific to this PR but it surfaces it more often, because RUF105 can rewrite a suppression before the redirect was applied. We can fix this as a separate issue but we should address it.
  • Then there's another ruff:ignore / noqa difference. Maybe you're already aware of it. But ruff:file-ignore does not support nested pragma comments like # explanation # ruff:file-ignore[F401] but file-level noqa comments do. That means, rewriting them breaks the suppression. Again, this is more ruff:ignore specific but something we should look into

Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comment.md Outdated
Comment on lines +127 to +129
This leaves an unused `noqa` comment to be cleaned up by `RUF100` instead, which can be especially
important in the case of a standalone `noqa` comment, which has no effect (in almost all cases), but
could become an effectful own-line `ruff:ignore` comment if `RUF105` applied.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about invalid rule codes? Does Ruff treat them as unused and removes them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This seems related to my response above, but yes, invalid-rule-code flags and offers to remove any invalid codes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We had a very long discussion around this behavior on the ty side #26426 for a similar rule

The conclusion was that the rule should also flag unused ignore comments. We may want to do the same here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I feel like it's a little different here since changing the comment type can change it from an inactive noqa comment to an active ruff:ignore comment, but after reading through #26426 again, I also don't want to debate it too much :)

I'm fine with emitting a diagnostic here and maybe making the fix unsafe or omitting it altogether.

Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comments.md
Comment thread crates/ruff_linter/src/rules/ruff/rules/noqa_comment.rs Outdated
Comment thread crates/ruff_linter/src/checkers/noqa.rs Outdated
Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comments.md
@ntBre

This comment was marked as resolved.

@ntBre

ntBre commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! I think I've addressed all of your comments.

I realized that there is still an issue with --ignore-noqa, but it turned out to affect other rules too. I added this to #26282:

# noqa: RUF100 remains silent because RUF100 skips emitting the diagnostic before --ignore-noqa can take effect. Similar pre-emission checks may affect other noqa-source rules.

Basically, I think we should restructure check_noqa a bit to make the phases of emitting and suppressing diagnostics a bit more clear and apply evenly to noqa and ruff:ignore. But this seems like (yet another) follow up.

@ntBre
ntBre requested a review from MichaReiser July 8, 2026 20:40
Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comment.md Outdated
Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comment.md Outdated
```

```py
import math # noqa: F401, EXT001

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think there's an argument that we should still flag F401. I'd be fine if we skip the autofix in this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to avoid the diagnostic if all codes are external and skip the fix if only some of them are external, as is the case here.

|
```

### External codes disable the rule

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I find the word disable the rule a bit misleading. It's not that we disable the rule. It's more that using noqa remains allowed in combination with external codes.

Comment on lines +127 to +129
This leaves an unused `noqa` comment to be cleaned up by `RUF100` instead, which can be especially
important in the case of a standalone `noqa` comment, which has no effect (in almost all cases), but
could become an effectful own-line `ruff:ignore` comment if `RUF105` applied.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We had a very long discussion around this behavior on the ty side #26426 for a similar rule

The conclusion was that the rule should also flag unused ignore comments. We may want to do the same here.

Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comments.md
|
```

### Nested pragma comment before the directive

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, you added the test here. The distance between the two tests is a bit long :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah sorry, I put this in the ## Inline comments section to test both the nesting and inline comment part.

Comment thread crates/ruff_linter/resources/mdtest/ruff/noqa-comments.md
Comment on lines +236 to +237
RUF105 flags blanket comments but does not offer a fix because `ruff:ignore` requires at least one
rule selector.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

and I think the complexity is that we don't track which rules were suppressed by a noqa comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It turns out that we actually do track this in matches:

pub(crate) struct NoqaDirectiveLine<'a> {
/// The range of the text line for which the noqa directive applies.
pub(crate) range: TextRange,
/// The noqa directive.
pub(crate) directive: Directive<'a>,
/// The codes that are ignored by the directive.
pub(crate) matches: Vec<Rule>,

I wonder why we don't use this in PGH004.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Happy to revert this if I went too far, but I got this working with matches without too much code. My only hesitation is whether this logic belongs exclusively in PGH004 or if it's okay to use it here too.

@MichaReiser

Copy link
Copy Markdown
Member

I asked codex whether this new rule name follows clippys (and our own) naming convention and it does not. It should be noqa-comments so that allow(noqa-comments) and ignore(noqa-comments) reads nicely. But I don't think we've been very consistent about this, and something we should revisit as part of human readable names

@ntBre
ntBre merged commit a907a12 into main Jul 9, 2026
47 checks passed
@ntBre
ntBre deleted the brent/noqa-comment branch July 9, 2026 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview mode features rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants