diff --git a/crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH004_3.py b/crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH004_3.py new file mode 100644 index 0000000000..49babf1e67 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pygrep_hooks/PGH004_3.py @@ -0,0 +1,8 @@ +# noqa +# ruff : noqa +# ruff: noqa: F401 +# ruff: noqa: PGH004 + + +# flake8: noqa +import math as m diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 25caddc082..e893551dfc 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -55,7 +55,7 @@ pub(crate) fn check_noqa( } match &exemption { - FileExemption::All => { + FileExemption::All(_) => { // If the file is exempted, ignore all diagnostics. ignored_diagnostics.push(index); continue; @@ -213,7 +213,17 @@ pub(crate) fn check_noqa( } } - if settings.rules.enabled(Rule::BlanketNOQA) { + if settings.rules.enabled(Rule::RedirectedNOQA) + && !per_file_ignores.contains(Rule::RedirectedNOQA) + && !exemption.includes(Rule::RedirectedNOQA) + { + ruff::rules::redirected_noqa(diagnostics, &noqa_directives); + } + + if settings.rules.enabled(Rule::BlanketNOQA) + && !per_file_ignores.contains(Rule::BlanketNOQA) + && !exemption.enumerates(Rule::BlanketNOQA) + { pygrep_hooks::rules::blanket_noqa( diagnostics, &noqa_directives, @@ -223,10 +233,6 @@ pub(crate) fn check_noqa( ); } - if settings.rules.enabled(Rule::RedirectedNOQA) { - ruff::rules::redirected_noqa(diagnostics, &noqa_directives); - } - ignored_diagnostics.sort_unstable(); ignored_diagnostics } diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index fa0fe1da6d..dae0b0cf66 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -280,7 +280,7 @@ pub(crate) fn rule_is_ignored( #[derive(Debug)] pub(crate) enum FileExemption<'a> { /// The file is exempt from all rules. - All, + All(Vec<&'a NoqaCode>), /// The file is exempt from the given rules. Codes(Vec<&'a NoqaCode>), } @@ -290,28 +290,38 @@ impl<'a> FileExemption<'a> { pub(crate) fn includes(&self, needle: Rule) -> bool { let needle = needle.noqa_code(); match self { - FileExemption::All => true, + FileExemption::All(_) => true, FileExemption::Codes(codes) => codes.iter().any(|code| needle == **code), } } + + /// Returns `true` if the file exemption lists the rule directly, rather than via a blanket + /// exemption. + pub(crate) fn enumerates(&self, needle: Rule) -> bool { + let needle = needle.noqa_code(); + let codes = match self { + FileExemption::All(codes) => codes, + FileExemption::Codes(codes) => codes, + }; + codes.iter().any(|code| needle == **code) + } } impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> { fn from(directives: &'a FileNoqaDirectives) -> Self { + let codes = directives + .lines() + .iter() + .flat_map(|line| &line.matches) + .collect(); if directives .lines() .iter() .any(|line| ParsedFileExemption::All == line.parsed_file_exemption) { - FileExemption::All + FileExemption::All(codes) } else { - FileExemption::Codes( - directives - .lines() - .iter() - .flat_map(|line| &line.matches) - .collect(), - ) + FileExemption::Codes(codes) } } } @@ -717,7 +727,7 @@ fn find_noqa_comments<'a>( // Mark any non-ignored diagnostics. for diagnostic in diagnostics { match &exemption { - FileExemption::All => { + FileExemption::All(_) => { // If the file is exempted, don't add any noqa directives. comments_by_line.push(None); continue; diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs b/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs index 96b9cb66e6..52df6929af 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/mod.rs @@ -19,6 +19,7 @@ mod tests { #[test_case(Rule::BlanketNOQA, Path::new("PGH004_0.py"))] #[test_case(Rule::BlanketNOQA, Path::new("PGH004_1.py"))] #[test_case(Rule::BlanketNOQA, Path::new("PGH004_2.py"))] + #[test_case(Rule::BlanketNOQA, Path::new("PGH004_3.py"))] #[test_case(Rule::InvalidMockAccess, Path::new("PGH005_0.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_3.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_3.py.snap new file mode 100644 index 0000000000..d5e81ab920 --- /dev/null +++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_3.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pygrep_hooks/mod.rs +--- +