From 98b95c9c38a36cfee572b072e07c634cf7b762f4 Mon Sep 17 00:00:00 2001 From: Max Mynter <32773644+maxmynter@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:05:59 +0200 Subject: [PATCH] Implement `Invalid rule provided` as rule RUF102 with `--fix` (#17138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #17084 ## Summary This PR adds a new rule (RUF102) to detect and fix invalid rule codes in `noqa` comments. Invalid rule codes in `noqa` directives serve no purpose and may indicate outdated code suppressions. This extends the previous behaviour originating from `crates/ruff_linter/src/noqa.rs` which would only emit a warnigs. With this rule a `--fix` is available. The rule: 1. Analyzes all `noqa` directives to identify invalid rule codes 2. Provides autofix functionality to: - Remove the entire comment if all codes are invalid - Remove only the invalid codes when mixed with valid codes 3. Preserves original comment formatting and whitespace where possible Example cases: - `# noqa: XYZ111` → Remove entire comment (keep empty line) - `# noqa: XYZ222, XYZ333` → Remove entire comment (keep empty line) - `# noqa: F401, INVALID123` → Keep only valid codes (`# noqa: F401`) ## Test Plan - Added tests in `crates/ruff_linter/resources/test/fixtures/ruff/RUF102.py` covering different example cases. ## Notes - This does not handle cases where parsing fails. E.g. `# noqa: NON_EXISTENT, ANOTHER_INVALID` causes a `LexicalError` and the diagnostic is not propagated and we cannot handle the diagnostic. I am also unsure what proper `fix` handling would be and making the user aware we don't understand the codes is probably the best bet. - The rule is added to the Preview rule group as it's a new addition ## Questions - Should we remove the warnings, now that we have a rule? - Is the current fix behavior appropriate for all cases, particularly the handling of whitespace and line deletions? - I'm new to the codebase; let me know if there are rule utilities which could have used but didn't. --------- Co-authored-by: Micha Reiser --- .../resources/test/fixtures/ruff/RUF102.py | 18 ++ crates/ruff_linter/src/checkers/noqa.rs | 7 + crates/ruff_linter/src/codes.rs | 1 + crates/ruff_linter/src/rules/ruff/mod.rs | 15 ++ .../src/rules/ruff/rules/invalid_rule_code.rs | 160 +++++++++++++ .../ruff_linter/src/rules/ruff/rules/mod.rs | 2 + ..._rules__ruff__tests__RUF102_RUF102.py.snap | 219 ++++++++++++++++++ ...sts__invalid_rule_code_external_rules.snap | 182 +++++++++++++++ ruff.schema.json | 1 + 9 files changed, 605 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF102.py create mode 100644 crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF102_RUF102.py.snap create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__invalid_rule_code_external_rules.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF102.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF102.py new file mode 100644 index 0000000000..a7385d177f --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF102.py @@ -0,0 +1,18 @@ +# Invalid code +import os # noqa: INVALID123 +# External code +import re # noqa: V123 +# Valid noqa +import sys # noqa: E402 +from functools import cache # Preceeding comment # noqa: F401, INVALID456 +from itertools import product # Preceeding comment # noqa: INVALID789 +# Succeeding comment +import math # noqa: INVALID000 # Succeeding comment +# Mixed valid and invalid +from typing import List # noqa: F401, INVALID123 +# Test for multiple invalid +from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +# Test for preserving valid codes when fixing +from itertools import chain # noqa: E402, INVALID300, F401 +# Test for mixed code types +import json # noqa: E402, INVALID400, V100 diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index be47bac452..785250f63d 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -227,6 +227,13 @@ pub(crate) fn check_noqa( ); } + if settings.rules.enabled(Rule::InvalidRuleCode) + && !per_file_ignores.contains(Rule::InvalidRuleCode) + && !exemption.enumerates(Rule::InvalidRuleCode) + { + ruff::rules::invalid_noqa_code(diagnostics, &noqa_directives, locator, &settings.external); + } + ignored_diagnostics.sort_unstable(); ignored_diagnostics } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 1d31cb69f0..3631a59b6f 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1016,6 +1016,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA), + (Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode), (Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml), #[cfg(any(feature = "test-rules", test))] diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 47b046330c..75c5930795 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -99,6 +99,7 @@ mod tests { #[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_3.py"))] #[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))] #[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))] + #[test_case(Rule::InvalidRuleCode, Path::new("RUF102.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( @@ -314,6 +315,20 @@ mod tests { Ok(()) } + #[test] + fn invalid_rule_code_external_rules() -> Result<()> { + let diagnostics = test_path( + Path::new("ruff/RUF102.py"), + &settings::LinterSettings { + external: vec!["V".to_string()], + ..settings::LinterSettings::for_rule(Rule::InvalidRuleCode) + }, + )?; + + assert_messages!(diagnostics); + Ok(()) + } + #[test] fn ruff_per_file_ignores() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs new file mode 100644 index 0000000000..997d311f10 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_rule_code.rs @@ -0,0 +1,160 @@ +use crate::noqa::{Code, Directive}; +use crate::registry::Rule; +use crate::Locator; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; + +use crate::noqa::{Codes, NoqaDirectives}; + +/// ## What it does +/// Checks for `noqa` codes that are invalid. +/// +/// ## Why is this bad? +/// Invalid rule codes serve no purpose and may indicate outdated code suppressions. +/// +/// ## Example +/// ```python +/// import os # noqa: XYZ999 +/// ``` +/// +/// Use instead: +/// ```python +/// import os +/// ``` +/// +/// Or if there are still valid codes needed: +/// ```python +/// import os # noqa: E402 +/// ``` +/// +/// ## Options +/// - `lint.external` +#[derive(ViolationMetadata)] +pub(crate) struct InvalidRuleCode { + pub(crate) rule_code: String, +} + +impl AlwaysFixableViolation for InvalidRuleCode { + #[derive_message_formats] + fn message(&self) -> String { + format!("Invalid rule code in `# noqa`: {}", self.rule_code) + } + + fn fix_title(&self) -> String { + "Remove the rule code".to_string() + } +} + +/// RUF102 for invalid noqa codes +pub(crate) fn invalid_noqa_code( + diagnostics: &mut Vec, + noqa_directives: &NoqaDirectives, + locator: &Locator, + external: &[String], +) { + for line in noqa_directives.lines() { + let Directive::Codes(directive) = &line.directive else { + continue; + }; + + let all_valid = directive.iter().all(|code| code_is_valid(code, external)); + + if all_valid { + continue; + } + + let (valid_codes, invalid_codes): (Vec<_>, Vec<_>) = directive + .iter() + .partition(|&code| code_is_valid(code, external)); + + if valid_codes.is_empty() { + diagnostics.push(all_codes_invalid_diagnostic(directive, invalid_codes)); + } else { + diagnostics.extend(invalid_codes.into_iter().map(|invalid_code| { + some_codes_are_invalid_diagnostic(directive, invalid_code, locator) + })); + } + } +} + +fn code_is_valid(code: &Code, external: &[String]) -> bool { + let code_str = code.as_str(); + Rule::from_code(code_str).is_ok() || external.iter().any(|ext| code_str.starts_with(ext)) +} + +fn all_codes_invalid_diagnostic( + directive: &Codes<'_>, + invalid_codes: Vec<&Code<'_>>, +) -> Diagnostic { + Diagnostic::new( + InvalidRuleCode { + rule_code: invalid_codes + .into_iter() + .map(Code::as_str) + .collect::>() + .join(", "), + }, + directive.range(), + ) + .with_fix(Fix::safe_edit(Edit::range_deletion(directive.range()))) +} + +fn some_codes_are_invalid_diagnostic( + codes: &Codes, + invalid_code: &Code, + locator: &Locator, +) -> Diagnostic { + let diagnostic = Diagnostic::new( + InvalidRuleCode { + rule_code: invalid_code.to_string(), + }, + invalid_code.range(), + ); + diagnostic.with_fix(Fix::safe_edit(remove_invalid_noqa( + codes, + invalid_code, + locator, + ))) +} + +fn remove_invalid_noqa(codes: &Codes, invalid_code: &Code, locator: &Locator) -> Edit { + // Is this the first code after the `:` that needs to get deleted + // For the first element, delete from after the `:` to the next comma (including) + // For any other element, delete from the previous comma (including) to the next comma (excluding) + let mut first = false; + + // Find the index of the previous comma or colon. + let start = locator + .slice(TextRange::new(codes.start(), invalid_code.start())) + .rmatch_indices([',', ':']) + .next() + .map(|(offset, text)| { + let offset = codes.start() + TextSize::try_from(offset).unwrap(); + if text == ":" { + first = true; + // Don't include the colon in the deletion range, or the noqa comment becomes invalid + offset + ':'.text_len() + } else { + offset + } + }) + .unwrap_or(invalid_code.start()); + + // Find the index of the trailing comma (if any) + let end = locator + .slice(TextRange::new(invalid_code.end(), codes.end())) + .find(',') + .map(|offset| { + let offset = invalid_code.end() + TextSize::try_from(offset).unwrap(); + + if first { + offset + ','.text_len() + } else { + offset + } + }) + .unwrap_or(invalid_code.end()); + + Edit::range_deletion(TextRange::new(start, end)) +} diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index c3b15f3565..b0aff0016e 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -19,6 +19,7 @@ pub(crate) use invalid_assert_message_literal_argument::*; pub(crate) use invalid_formatter_suppression_comment::*; pub(crate) use invalid_index_type::*; pub(crate) use invalid_pyproject_toml::*; +pub(crate) use invalid_rule_code::*; pub(crate) use map_int_version_parsing::*; pub(crate) use missing_fstring_syntax::*; pub(crate) use mutable_class_default::*; @@ -78,6 +79,7 @@ mod invalid_assert_message_literal_argument; mod invalid_formatter_suppression_comment; mod invalid_index_type; mod invalid_pyproject_toml; +mod invalid_rule_code; mod map_int_version_parsing; mod missing_fstring_syntax; mod mutable_class_default; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF102_RUF102.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF102_RUF102.py.snap new file mode 100644 index 0000000000..c70fa01b6c --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF102_RUF102.py.snap @@ -0,0 +1,219 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF102.py:2:12: RUF102 [*] Invalid rule code in `# noqa`: INVALID123 + | +1 | # Invalid code +2 | import os # noqa: INVALID123 + | ^^^^^^^^^^^^^^^^^^ RUF102 +3 | # External code +4 | import re # noqa: V123 + | + = help: Remove the rule code + +ℹ Safe fix +1 1 | # Invalid code +2 |-import os # noqa: INVALID123 + 2 |+import os +3 3 | # External code +4 4 | import re # noqa: V123 +5 5 | # Valid noqa + +RUF102.py:4:12: RUF102 [*] Invalid rule code in `# noqa`: V123 + | +2 | import os # noqa: INVALID123 +3 | # External code +4 | import re # noqa: V123 + | ^^^^^^^^^^^^ RUF102 +5 | # Valid noqa +6 | import sys # noqa: E402 + | + = help: Remove the rule code + +ℹ Safe fix +1 1 | # Invalid code +2 2 | import os # noqa: INVALID123 +3 3 | # External code +4 |-import re # noqa: V123 + 4 |+import re +5 5 | # Valid noqa +6 6 | import sys # noqa: E402 +7 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 + +RUF102.py:7:65: RUF102 [*] Invalid rule code in `# noqa`: INVALID456 + | +5 | # Valid noqa +6 | import sys # noqa: E402 +7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 + | ^^^^^^^^^^ RUF102 +8 | from itertools import product # Preceeding comment # noqa: INVALID789 +9 | # Succeeding comment + | + = help: Remove the rule code + +ℹ Safe fix +4 4 | import re # noqa: V123 +5 5 | # Valid noqa +6 6 | import sys # noqa: E402 +7 |-from functools import cache # Preceeding comment # noqa: F401, INVALID456 + 7 |+from functools import cache # Preceeding comment # noqa: F401 +8 8 | from itertools import product # Preceeding comment # noqa: INVALID789 +9 9 | # Succeeding comment +10 10 | import math # noqa: INVALID000 # Succeeding comment + +RUF102.py:8:53: RUF102 [*] Invalid rule code in `# noqa`: INVALID789 + | + 6 | import sys # noqa: E402 + 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 + 8 | from itertools import product # Preceeding comment # noqa: INVALID789 + | ^^^^^^^^^^^^^^^^^^ RUF102 + 9 | # Succeeding comment +10 | import math # noqa: INVALID000 # Succeeding comment + | + = help: Remove the rule code + +ℹ Safe fix +5 5 | # Valid noqa +6 6 | import sys # noqa: E402 +7 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 +8 |-from itertools import product # Preceeding comment # noqa: INVALID789 + 8 |+from itertools import product # Preceeding comment +9 9 | # Succeeding comment +10 10 | import math # noqa: INVALID000 # Succeeding comment +11 11 | # Mixed valid and invalid + +RUF102.py:10:13: RUF102 [*] Invalid rule code in `# noqa`: INVALID000 + | + 8 | from itertools import product # Preceeding comment # noqa: INVALID789 + 9 | # Succeeding comment +10 | import math # noqa: INVALID000 # Succeeding comment + | ^^^^^^^^^^^^^^^^^^ RUF102 +11 | # Mixed valid and invalid +12 | from typing import List # noqa: F401, INVALID123 + | + = help: Remove the rule code + +ℹ Safe fix +7 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 +8 8 | from itertools import product # Preceeding comment # noqa: INVALID789 +9 9 | # Succeeding comment +10 |-import math # noqa: INVALID000 # Succeeding comment + 10 |+import math # Succeeding comment +11 11 | # Mixed valid and invalid +12 12 | from typing import List # noqa: F401, INVALID123 +13 13 | # Test for multiple invalid + +RUF102.py:12:40: RUF102 [*] Invalid rule code in `# noqa`: INVALID123 + | +10 | import math # noqa: INVALID000 # Succeeding comment +11 | # Mixed valid and invalid +12 | from typing import List # noqa: F401, INVALID123 + | ^^^^^^^^^^ RUF102 +13 | # Test for multiple invalid +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + | + = help: Remove the rule code + +ℹ Safe fix +9 9 | # Succeeding comment +10 10 | import math # noqa: INVALID000 # Succeeding comment +11 11 | # Mixed valid and invalid +12 |-from typing import List # noqa: F401, INVALID123 + 12 |+from typing import List # noqa: F401 +13 13 | # Test for multiple invalid +14 14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +15 15 | # Test for preserving valid codes when fixing + +RUF102.py:14:46: RUF102 [*] Invalid rule code in `# noqa`: INVALID100 + | +12 | from typing import List # noqa: F401, INVALID123 +13 | # Test for multiple invalid +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + | ^^^^^^^^^^ RUF102 +15 | # Test for preserving valid codes when fixing +16 | from itertools import chain # noqa: E402, INVALID300, F401 + | + = help: Remove the rule code + +ℹ Safe fix +11 11 | # Mixed valid and invalid +12 12 | from typing import List # noqa: F401, INVALID123 +13 13 | # Test for multiple invalid +14 |-from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + 14 |+from collections import defaultdict # noqa: INVALID200, F401 +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types + +RUF102.py:14:58: RUF102 [*] Invalid rule code in `# noqa`: INVALID200 + | +12 | from typing import List # noqa: F401, INVALID123 +13 | # Test for multiple invalid +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + | ^^^^^^^^^^ RUF102 +15 | # Test for preserving valid codes when fixing +16 | from itertools import chain # noqa: E402, INVALID300, F401 + | + = help: Remove the rule code + +ℹ Safe fix +11 11 | # Mixed valid and invalid +12 12 | from typing import List # noqa: F401, INVALID123 +13 13 | # Test for multiple invalid +14 |-from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + 14 |+from collections import defaultdict # noqa: INVALID100, F401 +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types + +RUF102.py:16:44: RUF102 [*] Invalid rule code in `# noqa`: INVALID300 + | +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +15 | # Test for preserving valid codes when fixing +16 | from itertools import chain # noqa: E402, INVALID300, F401 + | ^^^^^^^^^^ RUF102 +17 | # Test for mixed code types +18 | import json # noqa: E402, INVALID400, V100 + | + = help: Remove the rule code + +ℹ Safe fix +13 13 | # Test for multiple invalid +14 14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +15 15 | # Test for preserving valid codes when fixing +16 |-from itertools import chain # noqa: E402, INVALID300, F401 + 16 |+from itertools import chain # noqa: E402, F401 +17 17 | # Test for mixed code types +18 18 | import json # noqa: E402, INVALID400, V100 + +RUF102.py:18:28: RUF102 [*] Invalid rule code in `# noqa`: INVALID400 + | +16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 | # Test for mixed code types +18 | import json # noqa: E402, INVALID400, V100 + | ^^^^^^^^^^ RUF102 + | + = help: Remove the rule code + +ℹ Safe fix +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types +18 |-import json # noqa: E402, INVALID400, V100 + 18 |+import json # noqa: E402, V100 + +RUF102.py:18:40: RUF102 [*] Invalid rule code in `# noqa`: V100 + | +16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 | # Test for mixed code types +18 | import json # noqa: E402, INVALID400, V100 + | ^^^^ RUF102 + | + = help: Remove the rule code + +ℹ Safe fix +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types +18 |-import json # noqa: E402, INVALID400, V100 + 18 |+import json # noqa: E402, INVALID400 diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__invalid_rule_code_external_rules.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__invalid_rule_code_external_rules.snap new file mode 100644 index 0000000000..2c2e4e88db --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__invalid_rule_code_external_rules.snap @@ -0,0 +1,182 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF102.py:2:12: RUF102 [*] Invalid rule code in `# noqa`: INVALID123 + | +1 | # Invalid code +2 | import os # noqa: INVALID123 + | ^^^^^^^^^^^^^^^^^^ RUF102 +3 | # External code +4 | import re # noqa: V123 + | + = help: Remove the rule code + +ℹ Safe fix +1 1 | # Invalid code +2 |-import os # noqa: INVALID123 + 2 |+import os +3 3 | # External code +4 4 | import re # noqa: V123 +5 5 | # Valid noqa + +RUF102.py:7:65: RUF102 [*] Invalid rule code in `# noqa`: INVALID456 + | +5 | # Valid noqa +6 | import sys # noqa: E402 +7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 + | ^^^^^^^^^^ RUF102 +8 | from itertools import product # Preceeding comment # noqa: INVALID789 +9 | # Succeeding comment + | + = help: Remove the rule code + +ℹ Safe fix +4 4 | import re # noqa: V123 +5 5 | # Valid noqa +6 6 | import sys # noqa: E402 +7 |-from functools import cache # Preceeding comment # noqa: F401, INVALID456 + 7 |+from functools import cache # Preceeding comment # noqa: F401 +8 8 | from itertools import product # Preceeding comment # noqa: INVALID789 +9 9 | # Succeeding comment +10 10 | import math # noqa: INVALID000 # Succeeding comment + +RUF102.py:8:53: RUF102 [*] Invalid rule code in `# noqa`: INVALID789 + | + 6 | import sys # noqa: E402 + 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 + 8 | from itertools import product # Preceeding comment # noqa: INVALID789 + | ^^^^^^^^^^^^^^^^^^ RUF102 + 9 | # Succeeding comment +10 | import math # noqa: INVALID000 # Succeeding comment + | + = help: Remove the rule code + +ℹ Safe fix +5 5 | # Valid noqa +6 6 | import sys # noqa: E402 +7 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 +8 |-from itertools import product # Preceeding comment # noqa: INVALID789 + 8 |+from itertools import product # Preceeding comment +9 9 | # Succeeding comment +10 10 | import math # noqa: INVALID000 # Succeeding comment +11 11 | # Mixed valid and invalid + +RUF102.py:10:13: RUF102 [*] Invalid rule code in `# noqa`: INVALID000 + | + 8 | from itertools import product # Preceeding comment # noqa: INVALID789 + 9 | # Succeeding comment +10 | import math # noqa: INVALID000 # Succeeding comment + | ^^^^^^^^^^^^^^^^^^ RUF102 +11 | # Mixed valid and invalid +12 | from typing import List # noqa: F401, INVALID123 + | + = help: Remove the rule code + +ℹ Safe fix +7 7 | from functools import cache # Preceeding comment # noqa: F401, INVALID456 +8 8 | from itertools import product # Preceeding comment # noqa: INVALID789 +9 9 | # Succeeding comment +10 |-import math # noqa: INVALID000 # Succeeding comment + 10 |+import math # Succeeding comment +11 11 | # Mixed valid and invalid +12 12 | from typing import List # noqa: F401, INVALID123 +13 13 | # Test for multiple invalid + +RUF102.py:12:40: RUF102 [*] Invalid rule code in `# noqa`: INVALID123 + | +10 | import math # noqa: INVALID000 # Succeeding comment +11 | # Mixed valid and invalid +12 | from typing import List # noqa: F401, INVALID123 + | ^^^^^^^^^^ RUF102 +13 | # Test for multiple invalid +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + | + = help: Remove the rule code + +ℹ Safe fix +9 9 | # Succeeding comment +10 10 | import math # noqa: INVALID000 # Succeeding comment +11 11 | # Mixed valid and invalid +12 |-from typing import List # noqa: F401, INVALID123 + 12 |+from typing import List # noqa: F401 +13 13 | # Test for multiple invalid +14 14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +15 15 | # Test for preserving valid codes when fixing + +RUF102.py:14:46: RUF102 [*] Invalid rule code in `# noqa`: INVALID100 + | +12 | from typing import List # noqa: F401, INVALID123 +13 | # Test for multiple invalid +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + | ^^^^^^^^^^ RUF102 +15 | # Test for preserving valid codes when fixing +16 | from itertools import chain # noqa: E402, INVALID300, F401 + | + = help: Remove the rule code + +ℹ Safe fix +11 11 | # Mixed valid and invalid +12 12 | from typing import List # noqa: F401, INVALID123 +13 13 | # Test for multiple invalid +14 |-from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + 14 |+from collections import defaultdict # noqa: INVALID200, F401 +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types + +RUF102.py:14:58: RUF102 [*] Invalid rule code in `# noqa`: INVALID200 + | +12 | from typing import List # noqa: F401, INVALID123 +13 | # Test for multiple invalid +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + | ^^^^^^^^^^ RUF102 +15 | # Test for preserving valid codes when fixing +16 | from itertools import chain # noqa: E402, INVALID300, F401 + | + = help: Remove the rule code + +ℹ Safe fix +11 11 | # Mixed valid and invalid +12 12 | from typing import List # noqa: F401, INVALID123 +13 13 | # Test for multiple invalid +14 |-from collections import defaultdict # noqa: INVALID100, INVALID200, F401 + 14 |+from collections import defaultdict # noqa: INVALID100, F401 +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types + +RUF102.py:16:44: RUF102 [*] Invalid rule code in `# noqa`: INVALID300 + | +14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +15 | # Test for preserving valid codes when fixing +16 | from itertools import chain # noqa: E402, INVALID300, F401 + | ^^^^^^^^^^ RUF102 +17 | # Test for mixed code types +18 | import json # noqa: E402, INVALID400, V100 + | + = help: Remove the rule code + +ℹ Safe fix +13 13 | # Test for multiple invalid +14 14 | from collections import defaultdict # noqa: INVALID100, INVALID200, F401 +15 15 | # Test for preserving valid codes when fixing +16 |-from itertools import chain # noqa: E402, INVALID300, F401 + 16 |+from itertools import chain # noqa: E402, F401 +17 17 | # Test for mixed code types +18 18 | import json # noqa: E402, INVALID400, V100 + +RUF102.py:18:28: RUF102 [*] Invalid rule code in `# noqa`: INVALID400 + | +16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 | # Test for mixed code types +18 | import json # noqa: E402, INVALID400, V100 + | ^^^^^^^^^^ RUF102 + | + = help: Remove the rule code + +ℹ Safe fix +15 15 | # Test for preserving valid codes when fixing +16 16 | from itertools import chain # noqa: E402, INVALID300, F401 +17 17 | # Test for mixed code types +18 |-import json # noqa: E402, INVALID400, V100 + 18 |+import json # noqa: E402, V100 diff --git a/ruff.schema.json b/ruff.schema.json index f5a2bc1e5a..37298eca81 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -4028,6 +4028,7 @@ "RUF10", "RUF100", "RUF101", + "RUF102", "RUF2", "RUF20", "RUF200",