crates/ruff_linter/resources/mdtest/suppression/ignore.md
ruff:ignore commentsruff:ignore rangeThese are regression tests for https://github.com/astral-sh/ruff/issues/25644, where ruff:ignore
comments behaved differently from noqa and ty:ignore comments when placed on the first line of a
diagnostic range.
[lint]
preview = true
select = ["RUF015"]
suppressed = [ # noqa: RUF015
*range(10)
][0]
not_suppressed = [ # ruff:ignore[RUF015]
*range(10)
][0]
ruff:ignore rangeThis case also was previously not suppressed but will be now. The B903 diagnostic covers the
entire class definition.
[lint]
preview = true
select = ["B903"]
# ruff:ignore[B903]
class Point:
def __init__(self, x: int):
self.x = x
Diagnostics with empty ranges should also be suppressible, as with noqa.
[lint]
preview = true
select = ["W292"]
suppressed = 1 # ruff:ignore[W292]
A block suppression should not apply to a diagnostic that starts inside the disabled range but ends
after the matching ruff:enable comment:
[lint]
preview = true
select = ["RUF015"]
# ruff:disable[RUF015]
# error: [unnecessary-iterable-allocation-for-first-element]
not_suppressed = [
# ruff:enable[RUF015]
*range(10)
][0]
This isn't strictly a problem because the formatter will indent and invalidate the ruff:enable
comment here, raising RUF103, but it seems better for this not to work regardless of formatting.
This is how the comments should look instead:
# ruff:disable[RUF015]
not_suppressed = [
*range(10)
][0]
# ruff:enable[RUF015]
[lint]
preview = true
select = ["RUF015"]
This case tests that both the range and suppression code are checked.
# error: [unnecessary-iterable-allocation-for-first-element]
not_suppressed = [ # ruff:ignore[F401]
*range(10)
][0]
[lint]
preview = true
select = [ "E" ]
This should be suppressed:
# ruff:ignore[E262]
x = 1 #bad
An ignore comment inside a multi-line statement only covers the next physical line, including its trailing comment:
values = [
# ruff:ignore[E262]
1, #bad
# error: [no-space-after-inline-comment]
2, #bad
]
An own-line ignore does not extend to a comment on the following line:
# ruff:ignore[E265]
x = 1
# error: [no-space-after-block-comment]
#bad
An own-line ignore above a multi-line statement covers a trailing comment on its final line:
# ruff:ignore[E262]
x = (
1
) #bad
[lint]
preview = true
select = [ "F" ]
Some diagnostics have a "parent" range, which should also be accounted for when suppressing them
with both noqa and ruff:ignore.
from foo import ( # noqa: F401
bar
)
from foo import ( # ruff:ignore[F401]
baz
)
[lint]
preview = true
select = [ "F401", "RUF100" ]
For cases with both a parent and non-parent noqa comment, the parent is marked as used, while the
non-parent, which falls later textually, is marked as unused.
from math import ( # noqa: F401
# error: [unused-noqa]
cos # noqa: F401
)
ruff:ignore should behave in the same way:
from sys import ( # ruff:ignore[F401]
# error: [unused-noqa]
argv # ruff:ignore[F401]
)
[lint]
preview = true
select = [ "W291" ]
For both logical and non-logical newlines:
<!-- fmt:off --># ruff:ignore[W291]
foo
values = [
# ruff:ignore[W291]
bar
]
ruff:ignore comments within a disable/enable pair[lint]
preview = true
select = ["E501", "F401", "RUF10"]
An intervening ruff:ignore directive shouldn't cause a disable/enable pair to be reported as
unmatched. Instead, the range suppression should take precedence, and the inner ruff:ignore should
be unused, just like a noqa comment:
# ruff:disable[F401]
# error: [unused-noqa]
import os # ruff:ignore[F401]
# ruff:enable[F401]
# ruff:disable[F401]
# error: [unused-noqa]
import sys # noqa: F401
# ruff:enable[F401]
This applies to own-line comments and nested comments too:
# ruff:disable[F401]
# error: [unused-noqa]
# ruff:ignore[F401]
import os
# error: [unused-noqa]
import sys # noqa: F401
# ruff:enable[F401]
# ruff:disable[F401]
# snapshot: unused-noqa
import sys # start # ruff:ignore[F401] # end
# ruff:enable[F401]
error[RUF100]: Unused suppression (unused: `F401`)
--> src/mdtest_snippet.py:21:21
|
21 | import sys # start # ruff:ignore[F401] # end
| ^^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
18 |
19 | # ruff:disable[F401]
20 | # snapshot: unused-noqa
- import sys # start # ruff:ignore[F401] # end
21 + import sys # start # end
22 | # ruff:enable[F401]
23 | # ruff:disable[E501]
24 | import os # ruff:ignore[F401]
note: This is an unsafe fix and may change runtime behavior
and cases where the disable and ignore suppress different codes:
# ruff:disable[E501]
import os # ruff:ignore[F401]
message = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
# ruff:enable[E501]
This should also work for ruff:ignore comments on the same line as the disable comment:
def f():
# error: [unused-noqa] "FIX002"
# ruff:disable[F401] # ruff:ignore[FIX002]
import os
# ruff:enable[F401]
file-ignore comments within a disable/enable pair[lint]
preview = true
select = ["F401", "RUF10"]
A file-ignore within a range suppression takes precedence and marks the disable as unused:
# error: [unused-noqa]
# ruff:disable[F401]
# ruff:file-ignore[F401]
import os
message = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
# ruff:enable[F401]
[lint]
preview = false
select = ["F401", "RUF102"]
With preview disabled, this should continue to emit F401, as well as RUF102:
# snapshot: invalid-rule-code
# ruff:disable[unused-import]
# error: [unused-import]
import math
# ruff:enable[unused-import]
error[RUF102]: Invalid rule code in suppression: unused-import
--> src/mdtest_snippet.py:2:16
|
2 | # ruff:disable[unused-import]
| ^^^^^^^^^^^^^
3 | # error: [unused-import]
4 | import math
5 | # ruff:enable[unused-import]
| -------------
|
help: Enable `lint.preview` to use rule names
help: Remove the suppression comment
1 | # snapshot: invalid-rule-code
- # ruff:disable[unused-import]
2 | # error: [unused-import]
3 | import math
- # ruff:enable[unused-import]
4 | # snapshot: invalid-rule-code
5 | # ruff:disable[unused-import, unknown-rule]
6 | # error: [unused-import]
Emit both (non-fix-title) help messages when rule names and unknown codes are present:
# snapshot: invalid-rule-code
# ruff:disable[unused-import, unknown-rule]
# error: [unused-import]
import sys
# ruff:enable[unused-import, unknown-rule]
error[RUF102]: Invalid rule code in suppression: unknown-rule, unused-import
--> src/mdtest_snippet.py:7:1
|
7 | # ruff:disable[unused-import, unknown-rule]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 | # error: [unused-import]
9 | import sys
10 | # ruff:enable[unused-import, unknown-rule]
| ------------------------------------------
|
help: Add non-Ruff rule codes to the `lint.external` configuration option
help: Enable `lint.preview` to use rule names
help: Remove the suppression comment
4 | import math
5 | # ruff:enable[unused-import]
6 | # snapshot: invalid-rule-code
- # ruff:disable[unused-import, unknown-rule]
7 | # error: [unused-import]
8 | import sys
- # ruff:enable[unused-import, unknown-rule]
Enable preview, unused-import and several RUF rules to check for valid suppression comments:
[lint]
preview = true
select = ["F401", "RUF100", "RUF102", "RUF103", "RUF104"]
ruff:ignoreThis comment should suppress the F401 diagnostic and not emit any other errors:
# ruff:ignore[unused-import]
import math
ruff:file-ignoreFile-level ignores should also work:
# ruff:file-ignore[unused-import]
import math
import sys
import traceback
ruff:disableAs should block-level ignores:
import math # error: [unused-import]
# ruff:disable[unused-import]
import sys
# ruff:enable[unused-import]
import traceback # error: [unused-import]
The disable and enable comments must match textually, even when a rule code and name identify
the same rule:
# error: [unmatched-suppression-comment]
# ruff:disable[unused-import]
import math
# snapshot: invalid-suppression-comment
# ruff:enable[F401]
error[RUF103]: Invalid suppression comment: no matching 'disable' comment
--> src/mdtest_snippet.py:12:1
|
12 | # ruff:enable[F401]
| ^^^^^^^^^^^^^^^^^^^
|
help: Remove suppression comment
9 | # ruff:disable[unused-import]
10 | import math
11 | # snapshot: invalid-suppression-comment
- # ruff:enable[F401]
note: This is an unsafe fix and may change runtime behavior
noqaOld-style noqa comments should continue to reject rule names:
# error: [unused-import]
import math # noqa: unused-import
but obviously continue working with rule codes:
import math # noqa: F401
invalid-rule-codeUnknown rule names should emit RUF102, while preserving valid names in the same suppression:
# snapshot: invalid-rule-code
# ruff:ignore[unused-import, not-a-rule]
import pathlib
error[RUF102]: Invalid rule code in suppression: not-a-rule
--> src/mdtest_snippet.py:2:30
|
2 | # ruff:ignore[unused-import, not-a-rule]
| ^^^^^^^^^^
|
help: Add non-Ruff rule codes to the `lint.external` configuration option
help: Remove the rule code `not-a-rule`
1 | # snapshot: invalid-rule-code
- # ruff:ignore[unused-import, not-a-rule]
2 + # ruff:ignore[unused-import]
3 | import pathlib
unused-noqaUnused suppressions with rule codes should still emit RUF100 with an appropriate error message:
# error: [unused-noqa]
import math # noqa: F401
# error: [unused-noqa]
import math # ruff:ignore[F401]
# snapshot: unused-noqa
import math # ruff:ignore[unused-import]
math.cos(1)
error[RUF100]: Unused suppression (unused: `unused-import`)
--> src/mdtest_snippet.py:8:14
|
8 | import math # ruff:ignore[unused-import]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
5 | import math # ruff:ignore[F401]
6 |
7 | # snapshot: unused-noqa
- import math # ruff:ignore[unused-import]
8 + import math
9 |
10 | math.cos(1)
11 | # snapshot: unused-noqa
A rule code and human-readable name for the same rule are treated as separate suppressions. The second suppression is therefore unused rather than duplicated:
# snapshot: unused-noqa
# ruff:ignore[F401, unused-import]
import pathlib
error[RUF100]: Unused suppression (unused: `unused-import`)
--> src/mdtest_snippet.py:12:1
|
12 | # ruff:ignore[F401, unused-import]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
9 |
10 | math.cos(1)
11 | # snapshot: unused-noqa
- # ruff:ignore[F401, unused-import]
12 + # ruff:ignore[F401]
13 | import pathlib
[lint]
preview = true
select = ["F401", "RUF10"]
ruff:ignore comments nested within other comments should still work:
import math # some comment # ruff:ignore[F401] # another comment
Invalid suppressions should only delete the suppression part:
# snapshot: invalid-suppression-comment
# error: [unused-import]
import sys # explanation # ruff:ignore # another
error[RUF103]: Invalid suppression comment: missing suppression codes like `[E501, ...]`
--> src/mdtest_snippet.py:4:27
|
4 | import sys # explanation # ruff:ignore # another
| ^^^^^^^^^^^^^^
|
help: Remove suppression comment
1 | import math # some comment # ruff:ignore[F401] # another comment
2 | # snapshot: invalid-suppression-comment
3 | # error: [unused-import]
- import sys # explanation # ruff:ignore # another
4 + import sys # explanation # another
5 | # error: [unmatched-suppression-comment]
6 | # error: [invalid-suppression-comment]
7 | # ruff:disable[F401] # ruff:enable[F401]
note: This is an unsafe fix and may change runtime behavior
Nested disable/enable comments on the same line are invalid. Instead of trying to match them up
to remove them in one operation, they are respectively treated as unmatched (disable) and invalid
(trailing enable).
# error: [unmatched-suppression-comment]
# error: [invalid-suppression-comment]
# ruff:disable[F401] # ruff:enable[F401]
import foo
[lint]
preview = true
select = ["F401", "RUF10"]
Nested disable and file-ignore comments are also invalid and don't suppress diagnostics on the
following line:
# snapshot: invalid-suppression-comment
# explanation # ruff:disable[F401]
# error: [unused-import]
import os
# error: [invalid-suppression-comment]
# explanation # ruff:file-ignore[F401]
# error: [unused-import]
import sys
error[RUF103]: Invalid suppression comment: trailing comments are only supported for ruff:ignore suppressions
--> src/mdtest_snippet.py:2:15
|
2 | # explanation # ruff:disable[F401]
| ^^^^^^^^^^^^^^^^^^^^
|
help: Remove suppression comment
1 | # snapshot: invalid-suppression-comment
- # explanation # ruff:disable[F401]
2 + # explanation
3 | # error: [unused-import]
4 | import os
5 |
note: This is an unsafe fix and may change runtime behavior
Similarly, a nested enable is invalid and doesn't re-enable a disabled rule:
# error: [unmatched-suppression-comment]
# ruff:disable[F401]
# error: [invalid-suppression-comment]
# comment # ruff:enable[F401]
import foo
[lint]
preview = true
select = ["F401", "RUF10", "FIX002"]
Nested suppression comments on a comment-only line are treated as trailing on the comment itself and don't suppress diagnostics on the following line:
# error: [unused-noqa]
# explanation # ruff:ignore[F401] # another
# error: [unused-import]
import pathlib
def foo():
# error: [unused-noqa]
# explanation # ruff:ignore[F401] # another
# error: [unused-import]
import pathlib
This can still be useful if the comment itself has a diagnostic:
# TODO this comment has a todo # noqa: FIX002
# TODO this comment has a todo # ruff:ignore[FIX002]
a = 10
unused-noqa[lint]
preview = true
select = ["E501", "F821", "RUF10"]
RUF100 should have an unsafe fix when deleting a leading suppression would change the placement
semantics of a later suppression. The file-ignore is initially invalid here, but removing the
unused ignore would make it valid, so the fix is unsafe:
# snapshot: unused-noqa
# error: [invalid-suppression-comment]
# ruff:ignore[E501] # ruff:file-ignore[F821]
# error: [undefined-name]
undefined_name
error[RUF100]: Unused suppression (unused: `E501`)
--> src/mdtest_snippet.py:3:1
|
3 | # ruff:ignore[E501] # ruff:file-ignore[F821]
| ^^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
1 | # snapshot: unused-noqa
2 | # error: [invalid-suppression-comment]
- # ruff:ignore[E501] # ruff:file-ignore[F821]
3 + # ruff:file-ignore[F821]
4 | # error: [undefined-name]
5 | undefined_name
6 | # snapshot: unused-noqa
note: This is an unsafe fix and may change runtime behavior
A later valid suppression also needs to be considered because removing the preceding comment can
change its semantics. In this case, removing the disable comment would transform the ignore from
a trailing comment on the disable comment to an own-line ignore comment, which would start
suppressing the F821 diagnostic:
# snapshot: unused-noqa
# error: [unused-noqa] "F821"
# ruff:disable[E501] # ruff:ignore[F821]
# error: [undefined-name]
undefined_name
# ruff:enable[E501]
error[RUF100]: Unused suppression (unused: `E501`)
--> src/mdtest_snippet.py:8:1
|
8 | # ruff:disable[E501] # ruff:ignore[F821]
| ^^^^^^^^^^^^^^^^^^^^^
9 | # error: [undefined-name]
10 | undefined_name
11 | # ruff:enable[E501]
| -------------------
|
help: Remove unused suppression
5 | undefined_name
6 | # snapshot: unused-noqa
7 | # error: [unused-noqa] "F821"
- # ruff:disable[E501] # ruff:ignore[F821]
8 + # ruff:ignore[F821]
9 | # error: [undefined-name]
10 | undefined_name
- # ruff:enable[E501]
note: This is an unsafe fix and may change runtime behavior
unused-noqa paired fix[lint]
preview = true
select = ["E501", "RUF10", "FIX002"]
Deleting either half of a disable/enable pair should make the fix unsafe if in a nested context:
# snapshot: unused-noqa
# ruff:disable[E501]
value = 1
# ruff:enable[E501] # TODO # ruff:ignore[FIX002]
error[RUF100]: Unused suppression (unused: `E501`)
--> src/mdtest_snippet.py:2:1
|
2 | # ruff:disable[E501]
| ^^^^^^^^^^^^^^^^^^^^
3 | value = 1
4 | # ruff:enable[E501] # TODO # ruff:ignore[FIX002]
| --------------------
|
help: Remove unused suppression
1 | # snapshot: unused-noqa
- # ruff:disable[E501]
2 | value = 1
- # ruff:enable[E501] # TODO # ruff:ignore[FIX002]
3 + # TODO # ruff:ignore[FIX002]
note: This is an unsafe fix and may change runtime behavior
unused-noqa partial fix[lint]
preview = true
select = ["E501", "F401", "F821", "RUF10"]
Removing a code from a multi-code suppression doesn't promote the later suppression, so the fix is still safe:
# snapshot: unused-noqa
# error: [invalid-suppression-comment]
# ruff:ignore[E501, F821] # ruff:file-ignore[F401]
undefined_name
error[RUF100]: Unused suppression (unused: `E501`)
--> src/mdtest_snippet.py:3:1
|
3 | # ruff:ignore[E501, F821] # ruff:file-ignore[F401]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
1 | # snapshot: unused-noqa
2 | # error: [invalid-suppression-comment]
- # ruff:ignore[E501, F821] # ruff:file-ignore[F401]
3 + # ruff:ignore[F821] # ruff:file-ignore[F401]
4 | undefined_name
invalid-rule-code[lint]
preview = true
select = ["F821", "RUF10"]
The RUF102 fix should also be unsafe when it would promote a later suppression:
# snapshot: invalid-rule-code
# error: [invalid-suppression-comment]
# ruff:ignore[XYZ] # ruff:file-ignore[F821]
# error: [undefined-name]
undefined_name
error[RUF102]: Invalid rule code in suppression: XYZ
--> src/mdtest_snippet.py:3:15
|
3 | # ruff:ignore[XYZ] # ruff:file-ignore[F821]
| ^^^
|
help: Add non-Ruff rule codes to the `lint.external` configuration option
help: Remove the suppression comment
1 | # snapshot: invalid-rule-code
2 | # error: [invalid-suppression-comment]
- # ruff:ignore[XYZ] # ruff:file-ignore[F821]
3 + # ruff:file-ignore[F821]
4 | # error: [undefined-name]
5 | undefined_name
note: This is an unsafe fix and may change runtime behavior
invalid-suppression-comment[lint]
preview = true
select = ["F401", "F821", "RUF10"]
The same applies to fixes for invalid suppression placement:
def f():
# snapshot: invalid-suppression-comment
# error: [unused-noqa]
# explanation # ruff:file-ignore[F401] # ruff:ignore[F401]
# error: [unused-import]
import os
error[RUF103]: Invalid suppression comment: trailing comments are only supported for ruff:ignore suppressions
--> src/mdtest_snippet.py:4:19
|
4 | # explanation # ruff:file-ignore[F401] # ruff:ignore[F401]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Remove suppression comment
1 | def f():
2 | # snapshot: invalid-suppression-comment
3 | # error: [unused-noqa]
- # explanation # ruff:file-ignore[F401] # ruff:ignore[F401]
4 + # explanation # ruff:ignore[F401]
5 | # error: [unused-import]
6 | import os
7 | # snapshot: invalid-suppression-comment
note: This is an unsafe fix and may change runtime behavior
And parse errors:
# snapshot: invalid-suppression-comment
# error: [unused-noqa]
# explanation # ruff:ignore # ruff:ignore[F821]
# error: [undefined-name]
undefined_name
error[RUF103]: Invalid suppression comment: missing suppression codes like `[E501, ...]`
--> src/mdtest_snippet.py:9:15
|
9 | # explanation # ruff:ignore # ruff:ignore[F821]
| ^^^^^^^^^^^^^^
|
help: Remove suppression comment
6 | import os
7 | # snapshot: invalid-suppression-comment
8 | # error: [unused-noqa]
- # explanation # ruff:ignore # ruff:ignore[F821]
9 + # explanation # ruff:ignore[F821]
10 | # error: [undefined-name]
11 | undefined_name
note: This is an unsafe fix and may change runtime behavior
[lint]
preview = true
select = ["RUF103"]
Parse errors should only highlight and delete the nested suppression:
# snapshot: invalid-suppression-comment
import os # explanation # ruff:unknown[F401] # another
# snapshot: invalid-suppression-comment
import sys # explanation # ruff:ignore[F401 F841] # another
error[RUF103]: Invalid suppression comment: unknown ruff directive
--> src/mdtest_snippet.py:2:26
|
2 | import os # explanation # ruff:unknown[F401] # another
| ^^^^^^^^^^^^^^^^^^^^^
|
help: Remove suppression comment
1 | # snapshot: invalid-suppression-comment
- import os # explanation # ruff:unknown[F401] # another
2 + import os # explanation # another
3 | # snapshot: invalid-suppression-comment
4 | import sys # explanation # ruff:ignore[F401 F841] # another
note: This is an unsafe fix and may change runtime behavior
error[RUF103]: Invalid suppression comment: missing comma between codes
--> src/mdtest_snippet.py:4:27
|
4 | import sys # explanation # ruff:ignore[F401 F841] # another
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Remove suppression comment
1 | # snapshot: invalid-suppression-comment
2 | import os # explanation # ruff:unknown[F401] # another
3 | # snapshot: invalid-suppression-comment
- import sys # explanation # ruff:ignore[F401 F841] # another
4 + import sys # explanation # another
note: This is an unsafe fix and may change runtime behavior
[lint]
preview = true
select = ["F401", "RUF103"]
A malformed nested suppression should not prevent a later valid suppression from being parsed:
# error: [invalid-suppression-comment]
import os # before # ruff:ignore # ruff:ignore[F401] # after
[lint]
preview = true
select = ["RUF100"]
Fixes for unused nested suppressions should preserve the surrounding comment fragments:
# snapshot: unused-noqa
value = 1 # before # ruff:ignore[F401] # after
# snapshot: unused-noqa
value = 1 # before # ruff:ignore[F401]
error[RUF100]: Unused suppression (non-enabled: `F401`)
--> src/mdtest_snippet.py:2:21
|
2 | value = 1 # before # ruff:ignore[F401] # after
| ^^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
1 | # snapshot: unused-noqa
- value = 1 # before # ruff:ignore[F401] # after
2 + value = 1 # before # after
3 |
4 | # snapshot: unused-noqa
5 | value = 1 # before # ruff:ignore[F401]
note: This is an unsafe fix and may change runtime behavior
error[RUF100]: Unused suppression (non-enabled: `F401`)
--> src/mdtest_snippet.py:5:21
|
5 | value = 1 # before # ruff:ignore[F401]
| ^^^^^^^^^^^^^^^^^^^
|
help: Remove unused suppression
2 | value = 1 # before # ruff:ignore[F401] # after
3 |
4 | # snapshot: unused-noqa
- value = 1 # before # ruff:ignore[F401]
5 + value = 1 # before
note: This is an unsafe fix and may change runtime behavior