Skip to content
Merged
14 changes: 14 additions & 0 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3442,6 +3442,20 @@ impl<'a> LintContext<'a> {
guard
}

/// Return a [`DiagnosticGuard`] for reporting a diagnostic, with its fix title deferred, if the
/// corresponding rule is enabled.
///
/// Prefer [`LintContext::report_diagnostic_if_enabled`] unless you need to attach
/// sub-diagnostics before the fix title. See its documentation for more details.
pub(crate) fn report_custom_diagnostic_if_enabled<'chk, T: Violation>(
&'chk self,
kind: T,
range: TextRange,
) -> Option<DiagnosticGuard<'chk, 'a>> {
self.is_rule_enabled(T::rule())
.then(|| self.report_custom_diagnostic(kind, range))
}

#[inline]
pub(crate) const fn is_rule_enabled(&self, rule: Rule) -> bool {
self.rules.enabled(rule)
Expand Down
4 changes: 2 additions & 2 deletions crates/ruff_linter/src/checkers/noqa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub(crate) fn check_noqa(
Directive::All(directive) => {
if matches.is_empty() {
let edit = delete_comment(directive.range(), locator);
let mut diagnostic = context.report_diagnostic(
let mut diagnostic = context.report_custom_diagnostic(
UnusedNOQA {
codes: None,
kind: ruff::rules::UnusedNOQAKind::Noqa,
Expand Down Expand Up @@ -223,7 +223,7 @@ pub(crate) fn check_noqa(
directive.range(),
)
};
let mut diagnostic = context.report_diagnostic(
let mut diagnostic = context.report_custom_diagnostic(
UnusedNOQA {
codes: Some(UnusedCodes {
disabled: disabled_codes
Expand Down
65 changes: 37 additions & 28 deletions crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ impl InvalidRuleCodeKind {
/// ```
///
/// ## Options
///
/// This rule will flag rule codes that are unknown to Ruff, even if they are
/// valid for other tools. You can tell Ruff to ignore such codes by configuring
/// the list of known "external" rule codes with the following option:
///
/// - `lint.external`
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.15.0")]
Expand Down Expand Up @@ -118,20 +123,22 @@ fn all_codes_invalid_diagnostic(
invalid_codes: Vec<&Code<'_>>,
context: &LintContext,
) {
context
.report_diagnostic(
InvalidRuleCode {
rule_code: invalid_codes
.into_iter()
.map(Code::as_str)
.collect::<Vec<_>>()
.join(", "),
kind: InvalidRuleCodeKind::Noqa,
whole_comment: true,
},
directive.range(),
)
.set_fix(Fix::safe_edit(Edit::range_deletion(directive.range())));
let mut diagnostic = context.report_custom_diagnostic(
InvalidRuleCode {
rule_code: invalid_codes
.into_iter()
.map(Code::as_str)
.collect::<Vec<_>>()
.join(", "),
kind: InvalidRuleCodeKind::Noqa,
whole_comment: true,
},
directive.range(),
);
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(directive.range())));
diagnostic.help(
"If this is a non-Ruff rule code, consider using the `lint.external` configuration option",
Comment thread
ntBre marked this conversation as resolved.
Outdated
);
}

fn some_codes_are_invalid_diagnostic(
Expand All @@ -140,20 +147,22 @@ fn some_codes_are_invalid_diagnostic(
locator: &Locator,
context: &LintContext,
) {
context
.report_diagnostic(
InvalidRuleCode {
rule_code: invalid_code.to_string(),
kind: InvalidRuleCodeKind::Noqa,
whole_comment: false,
},
invalid_code.range(),
)
.set_fix(Fix::safe_edit(remove_invalid_noqa(
codes,
invalid_code,
locator,
)));
let mut diagnostic = context.report_custom_diagnostic(
InvalidRuleCode {
rule_code: invalid_code.to_string(),
kind: InvalidRuleCodeKind::Noqa,
whole_comment: false,
},
invalid_code.range(),
);
diagnostic.set_fix(Fix::safe_edit(remove_invalid_noqa(
codes,
invalid_code,
locator,
)));
diagnostic.help(
"If this is a non-Ruff rule code, consider using the `lint.external` configuration option",
);
}

fn remove_invalid_noqa(codes: &Codes, invalid_code: &Code, locator: &Locator) -> Edit {
Expand Down
5 changes: 5 additions & 0 deletions crates/ruff_linter/src/rules/ruff/rules/unused_noqa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ impl UnusedNOQAKind {
/// ```
///
/// ## Options
///
/// This rule will flag rule codes that are unknown to Ruff, even if they are
/// valid for other tools. You can tell Ruff to ignore such codes by configuring
/// the list of known "external" rule codes with the following option:
///
/// - `lint.external`
///
/// ## References
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID123
3 | # External code
4 | import re # noqa: V123
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
1 | # Invalid code
- import os # noqa: INVALID123
Expand All @@ -28,6 +29,7 @@ RUF102 [*] Invalid rule code in `# noqa`: V123
5 | # Valid noqa
6 | import sys # noqa: E402
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
1 | # Invalid code
2 | import os # noqa: INVALID123
Expand All @@ -48,6 +50,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID456
8 | from itertools import product # Preceeding comment # noqa: INVALID789
9 | # Succeeding comment
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID456`
4 | import re # noqa: V123
5 | # Valid noqa
Expand All @@ -68,6 +71,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID789
9 | # Succeeding comment
10 | import math # noqa: INVALID000 # Succeeding comment
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
5 | # Valid noqa
6 | import sys # noqa: E402
Expand All @@ -88,6 +92,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID000
11 | # Mixed valid and invalid
12 | from typing import List # noqa: F401, INVALID123
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456
8 | from itertools import product # Preceeding comment # noqa: INVALID789
Expand All @@ -108,6 +113,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID123
13 | # Test for multiple invalid
14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID123`
9 | # Succeeding comment
10 | import math # noqa: INVALID000 # Succeeding comment
Expand All @@ -128,6 +134,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID100
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID100`
11 | # Mixed valid and invalid
12 | from typing import List # noqa: F401, INVALID123
Expand All @@ -148,6 +155,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID200
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID200`
11 | # Mixed valid and invalid
12 | from typing import List # noqa: F401, INVALID123
Expand All @@ -168,6 +176,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID300
17 | # Test for mixed code types
18 | import json # noqa: E402, INVALID400, V100
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID300`
13 | # Test for multiple invalid
14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401
Expand All @@ -188,6 +197,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID400
19 | # Test for rule redirects
20 | import pandas as pd # noqa: TCH002
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID400`
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
Expand All @@ -207,6 +217,7 @@ RUF102 [*] Invalid rule code in `# noqa`: V100
19 | # Test for rule redirects
20 | import pandas as pd # noqa: TCH002
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `V100`
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID123
3 | # External code
4 | import re # noqa: V123
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
1 | # Invalid code
- import os # noqa: INVALID123
Expand All @@ -28,6 +29,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID456
8 | from itertools import product # Preceeding comment # noqa: INVALID789
9 | # Succeeding comment
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID456`
4 | import re # noqa: V123
5 | # Valid noqa
Expand All @@ -48,6 +50,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID789
9 | # Succeeding comment
10 | import math # noqa: INVALID000 # Succeeding comment
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
5 | # Valid noqa
6 | import sys # noqa: E402
Expand All @@ -68,6 +71,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID000
11 | # Mixed valid and invalid
12 | from typing import List # noqa: F401, INVALID123
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the `# noqa` comment
7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456
8 | from itertools import product # Preceeding comment # noqa: INVALID789
Expand All @@ -88,6 +92,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID123
13 | # Test for multiple invalid
14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID123`
9 | # Succeeding comment
10 | import math # noqa: INVALID000 # Succeeding comment
Expand All @@ -108,6 +113,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID100
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID100`
11 | # Mixed valid and invalid
12 | from typing import List # noqa: F401, INVALID123
Expand All @@ -128,6 +134,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID200
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID200`
11 | # Mixed valid and invalid
12 | from typing import List # noqa: F401, INVALID123
Expand All @@ -148,6 +155,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID300
17 | # Test for mixed code types
18 | import json # noqa: E402, INVALID400, V100
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID300`
13 | # Test for multiple invalid
14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401
Expand All @@ -168,6 +176,7 @@ RUF102 [*] Invalid rule code in `# noqa`: INVALID400
19 | # Test for rule redirects
20 | import pandas as pd # noqa: TCH002
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `INVALID400`
15 | # Test for preserving valid codes when fixing
16 | from itertools import chain # noqa: E402, INVALID300, F401
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ RUF102 [*] Invalid rule code in suppression: YF829
97 | # ruff: enable[YF829]
| -----
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the suppression comment
90 |
91 | def f():
Expand All @@ -352,6 +353,7 @@ RUF102 [*] Invalid rule code in suppression: RQW320
| ------
97 | # ruff: enable[YF829]
|
help: If this is a non-Ruff rule code, consider using the `lint.external` configuration option
help: Remove the rule code `RQW320`
91 | def f():
92 | # Unknown rule codes
Expand Down
21 changes: 14 additions & 7 deletions crates/ruff_linter/src/suppression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ruff_python_trivia::{Cursor, indentation_at_offset};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize, TextSlice};
use smallvec::{SmallVec, smallvec};

use crate::checkers::ast::LintContext;
use crate::checkers::ast::{DiagnosticGuard, LintContext};
use crate::codes::Rule;
use crate::fix::edits::delete_comment;
use crate::rule_redirects::get_redirect_target;
Expand Down Expand Up @@ -231,7 +231,7 @@ impl Suppressions {
&& key.is_none_or(|key| key != *group_key)
{
if group.any_invalid() {
Suppressions::report_suppression_codes(
if let Some(mut diagnostic) = Suppressions::report_suppression_codes(
context,
locator,
group.suppression,
Expand All @@ -243,7 +243,12 @@ impl Suppressions {
whole_comment: group.suppression.codes().len()
== group.invalid_codes.len(),
},
);
) {
diagnostic.help(
"If this is a non-Ruff rule code, \
consider using the `lint.external` configuration option",
);
}
}
if group.any_unused() {
let mut codes = group.disabled_codes.clone();
Expand Down Expand Up @@ -367,22 +372,22 @@ impl Suppressions {
}
}

fn report_suppression_codes<T: Violation>(
context: &LintContext,
fn report_suppression_codes<'a, 'b, T: Violation>(
context: &'a LintContext<'b>,
locator: &Locator,
suppression: &Suppression,
remove_codes: &[&str],
highlight_only_code: bool,
kind: T,
) {
) -> Option<DiagnosticGuard<'a, 'b>> {
let disable_comment = suppression.comments.disable_comment();
let (range, edit) = Suppressions::delete_codes_or_comment(
locator,
disable_comment,
remove_codes,
highlight_only_code,
);
if let Some(mut diagnostic) = context.report_diagnostic_if_enabled(kind, range) {
if let Some(mut diagnostic) = context.report_custom_diagnostic_if_enabled(kind, range) {
if let Some(enable_comment) = suppression.comments.enable_comment() {
let (enable_range, enable_range_edit) = Suppressions::delete_codes_or_comment(
locator,
Expand All @@ -395,7 +400,9 @@ impl Suppressions {
} else {
diagnostic.set_fix(Fix::safe_edit(edit));
}
return Some(diagnostic);
}
None
}

fn delete_codes_or_comment(
Expand Down
Loading