diff --git a/README.md b/README.md index e070a4df8b..5f0f3485a8 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com | A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | | | A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | | | A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | | +| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension | | | | C403 | UnnecessaryListComprehensionSet | Unnecessary list comprehension - rewrite as a set comprehension | | | | C404 | UnnecessaryListComprehensionDict | Unnecessary list comprehension - rewrite as a dict comprehension | | | | SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 | diff --git a/resources/test/fixtures/C400.py b/resources/test/fixtures/C400.py new file mode 100644 index 0000000000..419203b4fe --- /dev/null +++ b/resources/test/fixtures/C400.py @@ -0,0 +1 @@ +x = list(x for x in range(3)) diff --git a/src/ast/checks.rs b/src/ast/checks.rs index c4f398f649..4a10bc49c5 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -714,6 +714,23 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &Vec) -> bool { } // flakes8-comprehensions +/// Check `list(generator)` compliance. +pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &Vec) -> Option { + if args.len() == 1 { + if let ExprKind::Name { id, .. } = &func.node { + if id == "list" { + if let ExprKind::GeneratorExp { .. } = &args[0].node { + return Some(Check::new( + CheckKind::UnnecessaryGeneratorList, + Range::from_located(expr), + )); + } + } + } + } + None +} + /// Check `set([...])` compliance. pub fn unnecessary_list_comprehension_set( expr: &Expr, diff --git a/src/check_ast.rs b/src/check_ast.rs index 308ab8c414..b496dbb42e 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -766,6 +766,12 @@ where } // flake8-comprehensions + if self.settings.enabled.contains(&CheckCode::C400) { + if let Some(check) = checks::unnecessary_generator_list(expr, func, args) { + self.checks.push(check); + }; + } + if self.settings.enabled.contains(&CheckCode::C403) { if let Some(check) = checks::unnecessary_list_comprehension_set(expr, func, args) diff --git a/src/checks.rs b/src/checks.rs index 2002353def..6e5b78773c 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -53,7 +53,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [ CheckCode::F901, ]; -pub const ALL_CHECK_CODES: [CheckCode; 55] = [ +pub const ALL_CHECK_CODES: [CheckCode; 56] = [ // pycodestyle CheckCode::E402, CheckCode::E501, @@ -103,6 +103,7 @@ pub const ALL_CHECK_CODES: [CheckCode; 55] = [ CheckCode::A002, CheckCode::A003, // flake8-comprehensions + CheckCode::C400, CheckCode::C403, CheckCode::C404, // flake8-super @@ -171,6 +172,7 @@ pub enum CheckCode { A002, A003, // flake8-comprehensions + C400, C403, C404, // flake8-super @@ -314,6 +316,7 @@ impl CheckCode { CheckCode::A002 => "A002", CheckCode::A003 => "A003", // flake8-comprehensions + CheckCode::C400 => "C400", CheckCode::C403 => "C403", CheckCode::C404 => "C404", // flake8-super @@ -395,6 +398,7 @@ impl CheckCode { CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()), CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()), // flake8-comprehensions + CheckCode::C400 => CheckKind::UnnecessaryGeneratorList, CheckCode::C403 => CheckKind::UnnecessaryListComprehensionSet, CheckCode::C404 => CheckKind::UnnecessaryListComprehensionDict, // flake8-super @@ -476,6 +480,7 @@ pub enum CheckKind { BuiltinArgumentShadowing(String), BuiltinAttributeShadowing(String), // flakes8-comprehensions + UnnecessaryGeneratorList, UnnecessaryListComprehensionSet, UnnecessaryListComprehensionDict, // flake8-super @@ -544,6 +549,7 @@ impl CheckKind { CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing", CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing", // flake8-comprehensions + CheckKind::UnnecessaryGeneratorList => "UnnecessaryGeneratorList", CheckKind::UnnecessaryListComprehensionSet => "UnnecessaryListComprehensionSet", CheckKind::UnnecessaryListComprehensionDict => "UnnecessaryListComprehensionDict", // flake8-super @@ -612,6 +618,7 @@ impl CheckKind { CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002, CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003, // flake8-comprehensions + CheckKind::UnnecessaryGeneratorList => &CheckCode::C400, CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403, CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404, // flake8-super @@ -769,6 +776,9 @@ impl CheckKind { format!("Class attribute `{name}` is shadowing a python builtin") } // flake8-comprehensions + CheckKind::UnnecessaryGeneratorList => { + "Unnecessary generator - rewrite as a list comprehension".to_string() + } CheckKind::UnnecessaryListComprehensionSet => { "Unnecessary list comprehension - rewrite as a set comprehension".to_string() } diff --git a/src/linter.rs b/src/linter.rs index 30643f69e8..291db73af5 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -786,6 +786,18 @@ mod tests { Ok(()) } + #[test] + fn c400() -> Result<()> { + let mut checks = check_path( + Path::new("./resources/test/fixtures/C400.py"), + &settings::Settings::for_rule(CheckCode::C400), + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(checks); + Ok(()) + } + #[test] fn c403() -> Result<()> { let mut checks = check_path( diff --git a/src/snapshots/ruff__linter__tests__c400.snap b/src/snapshots/ruff__linter__tests__c400.snap new file mode 100644 index 0000000000..b08637f53c --- /dev/null +++ b/src/snapshots/ruff__linter__tests__c400.snap @@ -0,0 +1,13 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: UnnecessaryGeneratorList + location: + row: 1 + column: 5 + end_location: + row: 1 + column: 30 + fix: ~ +