diff --git a/Cargo.lock b/Cargo.lock index a5001c6cc2..ee936529d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1926,6 +1926,7 @@ dependencies = [ "libcst", "log", "notify", + "num-bigint", "once_cell", "path-absolutize", "rayon", diff --git a/Cargo.toml b/Cargo.toml index f0daaa2ce4..b12c57b647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ update-informer = { version = "0.5.0", default_features = false, features = ["py walkdir = { version = "2.3.2" } strum = { version = "0.24.1", features = ["strum_macros"] } strum_macros = "0.24.3" +num-bigint = "0.4.3" [dev-dependencies] insta = { version = "1.19.1", features = ["yaml"] } diff --git a/README.md b/README.md index 0f10592ed7..875d9b9035 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com | C405 | UnnecessaryLiteralSet | Unnecessary literal - rewrite as a set literal | | | | C406 | UnnecessaryLiteralDict | Unnecessary literal - rewrite as a dict literal | | | | C408 | UnnecessaryCollectionCall | Unnecessary call - rewrite as a literal | | | +| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within () | | | | SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 | | T201 | PrintFound | `print` found | | 🛠 | | T203 | PPrintFound | `pprint` found | | 🛠 | diff --git a/resources/test/fixtures/C415.py b/resources/test/fixtures/C415.py new file mode 100644 index 0000000000..18dcb80818 --- /dev/null +++ b/resources/test/fixtures/C415.py @@ -0,0 +1,9 @@ +lst = [2, 1, 3] +a = set(lst[::-1]) +b = reversed(lst[::-1]) +c = sorted(lst[::-1]) +d = sorted(lst[::-1], reverse=True) +e = set(lst[2:-1]) +f = set(lst[:1:-1]) +g = set(lst[::1]) +h = set(lst[::-2]) diff --git a/src/ast/checks.rs b/src/ast/checks.rs index c9fa9388e4..4cc594db85 100644 --- a/src/ast/checks.rs +++ b/src/ast/checks.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use itertools::izip; +use num_bigint::BigInt; use regex::Regex; use rustpython_parser::ast::{ Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, @@ -985,6 +986,44 @@ pub fn unnecessary_collection_call( None } +pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option { + if let Some(first_arg) = args.first() { + if let ExprKind::Name { id, .. } = &func.node { + if id == "set" || id == "sorted" || id == "reversed" { + if let ExprKind::Subscript { slice, .. } = &first_arg.node { + if let ExprKind::Slice { lower, upper, step } = &slice.node { + if lower.is_none() && upper.is_none() { + if let Some(step) = step { + if let ExprKind::UnaryOp { + op: Unaryop::USub, + operand, + } = &step.node + { + if let ExprKind::Constant { + value: Constant::Int(val), + .. + } = &operand.node + { + if *val == BigInt::from(1) { + return Some(Check::new( + CheckKind::UnnecessarySubscriptReversal( + id.to_string(), + ), + Range::from_located(expr), + )); + } + } + } + } + } + } + } + } + } + } + None +} + // flake8-super /// Check that `super()` has no args pub fn check_super_args( diff --git a/src/check_ast.rs b/src/check_ast.rs index b23c818f0a..540cb94944 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -783,6 +783,12 @@ where }; } + if self.settings.enabled.contains(&CheckCode::C415) { + if let Some(check) = checks::unnecessary_subscript_reversal(expr, func, args) { + self.checks.push(check); + }; + } + // pyupgrade if self.settings.enabled.contains(&CheckCode::U002) && self.settings.target_version >= PythonVersion::Py310 diff --git a/src/checks.rs b/src/checks.rs index ebd7e5c0e9..03ccfa3b65 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -129,6 +129,7 @@ pub enum CheckCode { C405, C406, C408, + C415, // flake8-super SPR001, // flake8-print @@ -219,6 +220,7 @@ pub enum CheckKind { UnnecessaryLiteralSet(String), UnnecessaryLiteralDict(String), UnnecessaryCollectionCall(String), + UnnecessarySubscriptReversal(String), // flake8-super SuperCallWithParameters, // flake8-print @@ -312,6 +314,9 @@ impl CheckCode { CheckCode::C408 => { CheckKind::UnnecessaryCollectionCall("".to_string()) } + CheckCode::C415 => { + CheckKind::UnnecessarySubscriptReversal("".to_string()) + } // flake8-super CheckCode::SPR001 => CheckKind::SuperCallWithParameters, // flake8-print @@ -393,6 +398,7 @@ impl CheckKind { CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405, CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406, CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408, + CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415, // flake8-super CheckKind::SuperCallWithParameters => &CheckCode::SPR001, // flake8-print @@ -579,6 +585,9 @@ impl CheckKind { CheckKind::UnnecessaryCollectionCall(obj_type) => { format!("Unnecessary {obj_type} call - rewrite as a literal") } + CheckKind::UnnecessarySubscriptReversal(func) => { + format!("Unnecessary subscript reversal of iterable within {func}()") + } // flake8-super CheckKind::SuperCallWithParameters => { "Use `super()` instead of `super(__class__, self)`".to_string() diff --git a/src/linter.rs b/src/linter.rs index a9c65623b9..67ba8aeeca 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -894,6 +894,18 @@ mod tests { Ok(()) } + #[test] + fn c415() -> Result<()> { + let mut checks = check_path( + Path::new("./resources/test/fixtures/C415.py"), + &settings::Settings::for_rule(CheckCode::C415), + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(checks); + Ok(()) + } + #[test] fn spr001() -> Result<()> { let mut checks = check_path( diff --git a/src/snapshots/ruff__linter__tests__c415.snap b/src/snapshots/ruff__linter__tests__c415.snap new file mode 100644 index 0000000000..6e054c8109 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__c415.snap @@ -0,0 +1,41 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + UnnecessarySubscriptReversal: set + location: + row: 2 + column: 5 + end_location: + row: 2 + column: 19 + fix: ~ +- kind: + UnnecessarySubscriptReversal: reversed + location: + row: 3 + column: 5 + end_location: + row: 3 + column: 24 + fix: ~ +- kind: + UnnecessarySubscriptReversal: sorted + location: + row: 4 + column: 5 + end_location: + row: 4 + column: 22 + fix: ~ +- kind: + UnnecessarySubscriptReversal: sorted + location: + row: 5 + column: 5 + end_location: + row: 5 + column: 36 + fix: ~ +