From 781bbbc286f3208b4f7d9000c8eeac8c62ccbc1e Mon Sep 17 00:00:00 2001 From: Maksudul Haque Date: Thu, 29 Dec 2022 23:43:16 +0600 Subject: [PATCH] [pygrep-hooks] Adds Check for Blanket `# noqa` (#1440) --- README.md | 1 + .../test/fixtures/pygrep-hooks/PGH004_0.py | 11 ++++ ruff.schema.json | 1 + src/checkers/lines.rs | 11 +++- src/checkers/noqa.rs | 3 ++ src/checks.rs | 22 +++++--- src/checks_gen.rs | 25 +++++++-- src/noqa.rs | 22 ++++---- src/pygrep_hooks/mod.rs | 1 + src/pygrep_hooks/plugins/blanket_noqa.rs | 22 ++++++++ src/pygrep_hooks/plugins/mod.rs | 2 + ...grep_hooks__tests__PGH004_PGH004_0.py.snap | 53 +++++++++++++++++++ 12 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 resources/test/fixtures/pygrep-hooks/PGH004_0.py create mode 100644 src/pygrep_hooks/plugins/blanket_noqa.rs create mode 100644 src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH004_PGH004_0.py.snap diff --git a/README.md b/README.md index 2af8230549..54f0edd92c 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitH | PGH001 | NoEval | No builtin `eval()` allowed | | | PGH002 | DeprecatedLogWarn | `warn` is deprecated in favor of `warning` | | | PGH003 | BlanketTypeIgnore | Use specific error codes when ignoring type issues | | +| PGH004 | BlanketNOQA | Use specific error codes when using `noqa` | | ### Pylint (PLC, PLE, PLR, PLW) diff --git a/resources/test/fixtures/pygrep-hooks/PGH004_0.py b/resources/test/fixtures/pygrep-hooks/PGH004_0.py new file mode 100644 index 0000000000..c35f8e8824 --- /dev/null +++ b/resources/test/fixtures/pygrep-hooks/PGH004_0.py @@ -0,0 +1,11 @@ +x = 1 # noqa +x = 1 # NOQA:F401,W203 +# noqa +# NOQA +# noqa:F401 +# noqa:F401,W203 + +x = 1 +x = 1 # noqa: F401, W203 +# noqa: F401 +# noqa: F401, W203 diff --git a/ruff.schema.json b/ruff.schema.json index 178a63ec0d..c8e2cd08c9 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -726,6 +726,7 @@ "PGH001", "PGH002", "PGH003", + "PGH004", "PLC", "PLC0", "PLC04", diff --git a/src/checkers/lines.rs b/src/checkers/lines.rs index b68778a82e..eebc67993a 100644 --- a/src/checkers/lines.rs +++ b/src/checkers/lines.rs @@ -2,7 +2,7 @@ use crate::checks::{Check, CheckCode}; use crate::pycodestyle::checks::{line_too_long, no_newline_at_end_of_file}; -use crate::pygrep_hooks::plugins::blanket_type_ignore; +use crate::pygrep_hooks::plugins::{blanket_noqa, blanket_type_ignore}; use crate::pyupgrade::checks::unnecessary_coding_comment; use crate::settings::{flags, Settings}; @@ -18,6 +18,7 @@ pub fn check_lines( let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501); let enforce_no_newline_at_end_of_file = settings.enabled.contains(&CheckCode::W292); let enforce_blanket_type_ignore = settings.enabled.contains(&CheckCode::PGH003); + let enforce_blanket_noqa = settings.enabled.contains(&CheckCode::PGH004); let mut commented_lines_iter = commented_lines.iter().peekable(); for (index, line) in contents.lines().enumerate() { @@ -45,6 +46,14 @@ pub fn check_lines( } } } + + if enforce_blanket_noqa { + if commented_lines.contains(&(index + 1)) { + if let Some(check) = blanket_noqa(index, line) { + checks.push(check); + } + } + } } if enforce_line_too_long { diff --git a/src/checkers/noqa.rs b/src/checkers/noqa.rs index c1af2fd096..45a0126637 100644 --- a/src/checkers/noqa.rs +++ b/src/checkers/noqa.rs @@ -49,6 +49,9 @@ pub fn check_noqa( while let Some((index, check)) = checks_iter.next_if(|(_index, check)| check.location.row() <= *lineno) { + if check.kind == CheckKind::BlanketNOQA { + continue; + } // Grab the noqa (logical) line number for the current (physical) line. // If there are newlines at the end of the file, they won't be represented in // `noqa_line_for`, so fallback to the current line. diff --git a/src/checks.rs b/src/checks.rs index a3a126e370..35be6de6db 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -336,6 +336,7 @@ pub enum CheckCode { PGH001, PGH002, PGH003, + PGH004, // pandas-vet PD002, PD003, @@ -931,6 +932,7 @@ pub enum CheckKind { NoEval, DeprecatedLogWarn, BlanketTypeIgnore, + BlanketNOQA, // flake8-unused-arguments UnusedFunctionArgument(String), UnusedMethodArgument(String), @@ -980,9 +982,11 @@ impl CheckCode { pub fn lint_source(&self) -> &'static LintSource { match self { CheckCode::RUF100 => &LintSource::NoQA, - CheckCode::E501 | CheckCode::W292 | CheckCode::UP009 | CheckCode::PGH003 => { - &LintSource::Lines - } + CheckCode::E501 + | CheckCode::W292 + | CheckCode::UP009 + | CheckCode::PGH003 + | CheckCode::PGH004 => &LintSource::Lines, CheckCode::ERA001 | CheckCode::Q000 | CheckCode::Q001 @@ -1327,6 +1331,7 @@ impl CheckCode { CheckCode::PGH001 => CheckKind::NoEval, CheckCode::PGH002 => CheckKind::DeprecatedLogWarn, CheckCode::PGH003 => CheckKind::BlanketTypeIgnore, + CheckCode::PGH004 => CheckKind::BlanketNOQA, // flake8-unused-arguments CheckCode::ARG001 => CheckKind::UnusedFunctionArgument("...".to_string()), CheckCode::ARG002 => CheckKind::UnusedMethodArgument("...".to_string()), @@ -1594,6 +1599,7 @@ impl CheckCode { CheckCode::PGH001 => CheckCategory::PygrepHooks, CheckCode::PGH002 => CheckCategory::PygrepHooks, CheckCode::PGH003 => CheckCategory::PygrepHooks, + CheckCode::PGH004 => CheckCategory::PygrepHooks, CheckCode::PLC0414 => CheckCategory::Pylint, CheckCode::PLC2201 => CheckCategory::Pylint, CheckCode::PLC3002 => CheckCategory::Pylint, @@ -1955,6 +1961,7 @@ impl CheckKind { CheckKind::NoEval => &CheckCode::PGH001, CheckKind::DeprecatedLogWarn => &CheckCode::PGH002, CheckKind::BlanketTypeIgnore => &CheckCode::PGH003, + CheckKind::BlanketNOQA => &CheckCode::PGH004, // flake8-unused-arguments CheckKind::UnusedFunctionArgument(..) => &CheckCode::ARG001, CheckKind::UnusedMethodArgument(..) => &CheckCode::ARG002, @@ -2816,13 +2823,14 @@ impl CheckKind { "Boolean positional value in function call".to_string() } // pygrep-hooks - CheckKind::NoEval => "No builtin `eval()` allowed".to_string(), - CheckKind::DeprecatedLogWarn => { - "`warn` is deprecated in favor of `warning`".to_string() - } + CheckKind::BlanketNOQA => "Use specific error codes when using `noqa`".to_string(), CheckKind::BlanketTypeIgnore => { "Use specific error codes when ignoring type issues".to_string() } + CheckKind::DeprecatedLogWarn => { + "`warn` is deprecated in favor of `warning`".to_string() + } + CheckKind::NoEval => "No builtin `eval()` allowed".to_string(), // flake8-unused-arguments CheckKind::UnusedFunctionArgument(name) => { format!("Unused function argument: `{name}`") diff --git a/src/checks_gen.rs b/src/checks_gen.rs index c00970f9ad..77b3402fde 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -377,6 +377,7 @@ pub enum CheckCodePrefix { PGH001, PGH002, PGH003, + PGH004, PLC, PLC0, PLC04, @@ -857,6 +858,7 @@ impl CheckCodePrefix { CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003, + CheckCode::PGH004, CheckCode::PD002, CheckCode::PD003, CheckCode::PD004, @@ -2073,12 +2075,28 @@ impl CheckCodePrefix { ); vec![CheckCode::PD901] } - CheckCodePrefix::PGH => vec![CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003], - CheckCodePrefix::PGH0 => vec![CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003], - CheckCodePrefix::PGH00 => vec![CheckCode::PGH001, CheckCode::PGH002, CheckCode::PGH003], + CheckCodePrefix::PGH => vec![ + CheckCode::PGH001, + CheckCode::PGH002, + CheckCode::PGH003, + CheckCode::PGH004, + ], + CheckCodePrefix::PGH0 => vec![ + CheckCode::PGH001, + CheckCode::PGH002, + CheckCode::PGH003, + CheckCode::PGH004, + ], + CheckCodePrefix::PGH00 => vec![ + CheckCode::PGH001, + CheckCode::PGH002, + CheckCode::PGH003, + CheckCode::PGH004, + ], CheckCodePrefix::PGH001 => vec![CheckCode::PGH001], CheckCodePrefix::PGH002 => vec![CheckCode::PGH002], CheckCodePrefix::PGH003 => vec![CheckCode::PGH003], + CheckCodePrefix::PGH004 => vec![CheckCode::PGH004], CheckCodePrefix::PLC => { vec![CheckCode::PLC0414, CheckCode::PLC2201, CheckCode::PLC3002] } @@ -3152,6 +3170,7 @@ impl CheckCodePrefix { CheckCodePrefix::PGH001 => SuffixLength::Three, CheckCodePrefix::PGH002 => SuffixLength::Three, CheckCodePrefix::PGH003 => SuffixLength::Three, + CheckCodePrefix::PGH004 => SuffixLength::Three, CheckCodePrefix::PLC => SuffixLength::Zero, CheckCodePrefix::PLC0 => SuffixLength::One, CheckCodePrefix::PLC04 => SuffixLength::Two, diff --git a/src/noqa.rs b/src/noqa.rs index e86b64d54c..5c7e34f0f8 100644 --- a/src/noqa.rs +++ b/src/noqa.rs @@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::checks::{Check, CheckCode, CODE_REDIRECTS}; -static NO_QA_LINE_REGEX: Lazy = Lazy::new(|| { +static NOQA_LINE_REGEX: Lazy = Lazy::new(|| { Regex::new( r"(?P\s*)(?P(?i:# noqa)(?::\s?(?P([A-Z]+[0-9]+(?:[,\s]+)?)+))?)", ) @@ -39,7 +39,7 @@ pub enum Directive<'a> { /// Extract the noqa `Directive` from a line of Python source code. pub fn extract_noqa_directive(line: &str) -> Directive { - match NO_QA_LINE_REGEX.captures(line) { + match NOQA_LINE_REGEX.captures(line) { Some(caps) => match caps.name("spaces") { Some(spaces) => match caps.name("noqa") { Some(noqa) => match caps.name("codes") { @@ -206,20 +206,20 @@ mod tests { use crate::ast::types::Range; use crate::checks::{Check, CheckKind}; - use crate::noqa::{add_noqa_inner, NO_QA_LINE_REGEX}; + use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX}; #[test] fn regex() { - assert!(NO_QA_LINE_REGEX.is_match("# noqa")); - assert!(NO_QA_LINE_REGEX.is_match("# NoQA")); + assert!(NOQA_LINE_REGEX.is_match("# noqa")); + assert!(NOQA_LINE_REGEX.is_match("# NoQA")); - assert!(NO_QA_LINE_REGEX.is_match("# noqa: F401")); - assert!(NO_QA_LINE_REGEX.is_match("# NoQA: F401")); - assert!(NO_QA_LINE_REGEX.is_match("# noqa: F401, E501")); + assert!(NOQA_LINE_REGEX.is_match("# noqa: F401")); + assert!(NOQA_LINE_REGEX.is_match("# NoQA: F401")); + assert!(NOQA_LINE_REGEX.is_match("# noqa: F401, E501")); - assert!(NO_QA_LINE_REGEX.is_match("# noqa:F401")); - assert!(NO_QA_LINE_REGEX.is_match("# NoQA:F401")); - assert!(NO_QA_LINE_REGEX.is_match("# noqa:F401, E501")); + assert!(NOQA_LINE_REGEX.is_match("# noqa:F401")); + assert!(NOQA_LINE_REGEX.is_match("# NoQA:F401")); + assert!(NOQA_LINE_REGEX.is_match("# noqa:F401, E501")); } #[test] diff --git a/src/pygrep_hooks/mod.rs b/src/pygrep_hooks/mod.rs index 50cba4fa8b..d258c92766 100644 --- a/src/pygrep_hooks/mod.rs +++ b/src/pygrep_hooks/mod.rs @@ -17,6 +17,7 @@ mod tests { #[test_case(CheckCode::PGH002, Path::new("PGH002_0.py"); "PGH002_0")] #[test_case(CheckCode::PGH002, Path::new("PGH002_1.py"); "PGH002_1")] #[test_case(CheckCode::PGH003, Path::new("PGH003_0.py"); "PGH003_0")] + #[test_case(CheckCode::PGH004, Path::new("PGH004_0.py"); "PGH004_0")] fn checks(check_code: CheckCode, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); let mut checks = test_path( diff --git a/src/pygrep_hooks/plugins/blanket_noqa.rs b/src/pygrep_hooks/plugins/blanket_noqa.rs new file mode 100644 index 0000000000..cbfcc12665 --- /dev/null +++ b/src/pygrep_hooks/plugins/blanket_noqa.rs @@ -0,0 +1,22 @@ +use once_cell::sync::Lazy; +use regex::Regex; +use rustpython_ast::Location; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; + +static BLANKET_NOQA_REGEX: Lazy = + Lazy::new(|| Regex::new(r"(?i)# noqa($|\s|:[^ ])").unwrap()); + +/// PGH004 - use of blanket noqa comments +pub fn blanket_noqa(lineno: usize, line: &str) -> Option { + BLANKET_NOQA_REGEX.find(line).map(|m| { + Check::new( + CheckKind::BlanketNOQA, + Range { + location: Location::new(lineno + 1, m.start()), + end_location: Location::new(lineno + 1, m.end()), + }, + ) + }) +} diff --git a/src/pygrep_hooks/plugins/mod.rs b/src/pygrep_hooks/plugins/mod.rs index 7c43f149db..744c2cbe91 100644 --- a/src/pygrep_hooks/plugins/mod.rs +++ b/src/pygrep_hooks/plugins/mod.rs @@ -1,7 +1,9 @@ +pub use blanket_noqa::blanket_noqa; pub use blanket_type_ignore::blanket_type_ignore; pub use deprecated_log_warn::deprecated_log_warn; pub use no_eval::no_eval; +mod blanket_noqa; mod blanket_type_ignore; mod deprecated_log_warn; mod no_eval; diff --git a/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH004_PGH004_0.py.snap b/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH004_PGH004_0.py.snap new file mode 100644 index 0000000000..9550b86d41 --- /dev/null +++ b/src/pygrep_hooks/snapshots/ruff__pygrep_hooks__tests__PGH004_PGH004_0.py.snap @@ -0,0 +1,53 @@ +--- +source: src/pygrep_hooks/mod.rs +expression: checks +--- +- kind: BlanketNoqa + location: + row: 1 + column: 7 + end_location: + row: 1 + column: 13 + fix: ~ +- kind: BlanketNoqa + location: + row: 2 + column: 7 + end_location: + row: 2 + column: 15 + fix: ~ +- kind: BlanketNoqa + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 6 + fix: ~ +- kind: BlanketNoqa + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 6 + fix: ~ +- kind: BlanketNoqa + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 8 + fix: ~ +- kind: BlanketNoqa + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 8 + fix: ~ +