Skip to content

Commit 4e67a21

Browse files
authored
apply range suppressions to filter diagnostics (astral-sh#21623)
Builds on range suppressions from astral-sh#21441 Filters diagnostics based on parsed valid range suppressions. Issue: astral-sh#3711
1 parent 8ea1896 commit 4e67a21

File tree

14 files changed

+564
-12
lines changed

14 files changed

+564
-12
lines changed

crates/ruff/tests/cli/lint.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,78 @@ def function():
14401440
Ok(())
14411441
}
14421442

1443+
#[test]
1444+
fn ignore_noqa() -> Result<()> {
1445+
let fixture = CliTest::new()?;
1446+
fixture.write_file(
1447+
"ruff.toml",
1448+
r#"
1449+
[lint]
1450+
select = ["F401"]
1451+
"#,
1452+
)?;
1453+
1454+
fixture.write_file(
1455+
"noqa.py",
1456+
r#"
1457+
import os # noqa: F401
1458+
1459+
# ruff: disable[F401]
1460+
import sys
1461+
"#,
1462+
)?;
1463+
1464+
// without --ignore-noqa
1465+
assert_cmd_snapshot!(fixture
1466+
.check_command()
1467+
.args(["--config", "ruff.toml"])
1468+
.arg("noqa.py"),
1469+
@r"
1470+
success: false
1471+
exit_code: 1
1472+
----- stdout -----
1473+
noqa.py:5:8: F401 [*] `sys` imported but unused
1474+
Found 1 error.
1475+
[*] 1 fixable with the `--fix` option.
1476+
1477+
----- stderr -----
1478+
");
1479+
1480+
assert_cmd_snapshot!(fixture
1481+
.check_command()
1482+
.args(["--config", "ruff.toml"])
1483+
.arg("noqa.py")
1484+
.args(["--preview"]),
1485+
@r"
1486+
success: true
1487+
exit_code: 0
1488+
----- stdout -----
1489+
All checks passed!
1490+
1491+
----- stderr -----
1492+
");
1493+
1494+
// with --ignore-noqa --preview
1495+
assert_cmd_snapshot!(fixture
1496+
.check_command()
1497+
.args(["--config", "ruff.toml"])
1498+
.arg("noqa.py")
1499+
.args(["--ignore-noqa", "--preview"]),
1500+
@r"
1501+
success: false
1502+
exit_code: 1
1503+
----- stdout -----
1504+
noqa.py:2:8: F401 [*] `os` imported but unused
1505+
noqa.py:5:8: F401 [*] `sys` imported but unused
1506+
Found 2 errors.
1507+
[*] 2 fixable with the `--fix` option.
1508+
1509+
----- stderr -----
1510+
");
1511+
1512+
Ok(())
1513+
}
1514+
14431515
#[test]
14441516
fn add_noqa() -> Result<()> {
14451517
let fixture = CliTest::new()?;
@@ -1632,6 +1704,100 @@ def unused(x): # noqa: ANN001, ARG001, D103
16321704
Ok(())
16331705
}
16341706

1707+
#[test]
1708+
fn add_noqa_existing_file_level_noqa() -> Result<()> {
1709+
let fixture = CliTest::new()?;
1710+
fixture.write_file(
1711+
"ruff.toml",
1712+
r#"
1713+
[lint]
1714+
select = ["F401"]
1715+
"#,
1716+
)?;
1717+
1718+
fixture.write_file(
1719+
"noqa.py",
1720+
r#"
1721+
# ruff: noqa F401
1722+
import os
1723+
"#,
1724+
)?;
1725+
1726+
assert_cmd_snapshot!(fixture
1727+
.check_command()
1728+
.args(["--config", "ruff.toml"])
1729+
.arg("noqa.py")
1730+
.arg("--preview")
1731+
.args(["--add-noqa"])
1732+
.arg("-")
1733+
.pass_stdin(r#"
1734+
1735+
"#), @r"
1736+
success: true
1737+
exit_code: 0
1738+
----- stdout -----
1739+
1740+
----- stderr -----
1741+
");
1742+
1743+
let test_code =
1744+
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
1745+
1746+
insta::assert_snapshot!(test_code, @r"
1747+
# ruff: noqa F401
1748+
import os
1749+
");
1750+
1751+
Ok(())
1752+
}
1753+
1754+
#[test]
1755+
fn add_noqa_existing_range_suppression() -> Result<()> {
1756+
let fixture = CliTest::new()?;
1757+
fixture.write_file(
1758+
"ruff.toml",
1759+
r#"
1760+
[lint]
1761+
select = ["F401"]
1762+
"#,
1763+
)?;
1764+
1765+
fixture.write_file(
1766+
"noqa.py",
1767+
r#"
1768+
# ruff: disable[F401]
1769+
import os
1770+
"#,
1771+
)?;
1772+
1773+
assert_cmd_snapshot!(fixture
1774+
.check_command()
1775+
.args(["--config", "ruff.toml"])
1776+
.arg("noqa.py")
1777+
.arg("--preview")
1778+
.args(["--add-noqa"])
1779+
.arg("-")
1780+
.pass_stdin(r#"
1781+
1782+
"#), @r"
1783+
success: true
1784+
exit_code: 0
1785+
----- stdout -----
1786+
1787+
----- stderr -----
1788+
");
1789+
1790+
let test_code =
1791+
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
1792+
1793+
insta::assert_snapshot!(test_code, @r"
1794+
# ruff: disable[F401]
1795+
import os
1796+
");
1797+
1798+
Ok(())
1799+
}
1800+
16351801
#[test]
16361802
fn add_noqa_multiline_comment() -> Result<()> {
16371803
let fixture = CliTest::new()?;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
def f():
2+
# These should both be ignored by the range suppression.
3+
# ruff: disable[E741, F841]
4+
I = 1
5+
# ruff: enable[E741, F841]
6+
7+
8+
def f():
9+
# These should both be ignored by the implicit range suppression.
10+
# Should also generate an "unmatched suppression" warning.
11+
# ruff:disable[E741,F841]
12+
I = 1
13+
14+
15+
def f():
16+
# Neither warning is ignored, and an "unmatched suppression"
17+
# should be generated.
18+
I = 1
19+
# ruff: enable[E741, F841]
20+
21+
22+
def f():
23+
# One should be ignored by the range suppression, and
24+
# the other logged to the user.
25+
# ruff: disable[E741]
26+
I = 1
27+
# ruff: enable[E741]
28+
29+
30+
def f():
31+
# Test interleaved range suppressions. The first and last
32+
# lines should each log a different warning, while the
33+
# middle line should be completely silenced.
34+
# ruff: disable[E741]
35+
l = 0
36+
# ruff: disable[F841]
37+
O = 1
38+
# ruff: enable[E741]
39+
I = 2
40+
# ruff: enable[F841]
41+
42+
43+
def f():
44+
# Neither of these are ignored and warnings are
45+
# logged to user
46+
# ruff: disable[E501]
47+
I = 1
48+
# ruff: enable[E501]
49+
50+
51+
def f():
52+
# These should both be ignored by the range suppression,
53+
# and an unusued noqa diagnostic should be logged.
54+
# ruff:disable[E741,F841]
55+
I = 1 # noqa: E741,F841
56+
# ruff:enable[E741,F841]

crates/ruff_linter/src/checkers/noqa.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ use crate::fix::edits::delete_comment;
1212
use crate::noqa::{
1313
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
1414
};
15+
use crate::preview::is_range_suppressions_enabled;
1516
use crate::registry::Rule;
1617
use crate::rule_redirects::get_redirect_target;
1718
use crate::rules::pygrep_hooks;
1819
use crate::rules::ruff;
1920
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
2021
use crate::settings::LinterSettings;
22+
use crate::suppression::Suppressions;
2123
use crate::{Edit, Fix, Locator};
2224

2325
use super::ast::LintContext;
2426

2527
/// RUF100
28+
#[expect(clippy::too_many_arguments)]
2629
pub(crate) fn check_noqa(
2730
context: &mut LintContext,
2831
path: &Path,
@@ -31,6 +34,7 @@ pub(crate) fn check_noqa(
3134
noqa_line_for: &NoqaMapping,
3235
analyze_directives: bool,
3336
settings: &LinterSettings,
37+
suppressions: &Suppressions,
3438
) -> Vec<usize> {
3539
// Identify any codes that are globally exempted (within the current file).
3640
let file_noqa_directives =
@@ -40,7 +44,7 @@ pub(crate) fn check_noqa(
4044
let mut noqa_directives =
4145
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
4246

43-
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
47+
if file_noqa_directives.is_empty() && noqa_directives.is_empty() && suppressions.is_empty() {
4448
return Vec::new();
4549
}
4650

@@ -60,11 +64,19 @@ pub(crate) fn check_noqa(
6064
continue;
6165
}
6266

67+
// Apply file-level suppressions first
6368
if exemption.contains_secondary_code(code) {
6469
ignored_diagnostics.push(index);
6570
continue;
6671
}
6772

73+
// Apply ranged suppressions next
74+
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
75+
ignored_diagnostics.push(index);
76+
continue;
77+
}
78+
79+
// Apply end-of-line noqa suppressions last
6880
let noqa_offsets = diagnostic
6981
.parent()
7082
.into_iter()

0 commit comments

Comments
 (0)