diff --git a/README.md b/README.md index a63e5f064b..9a11526168 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ ruff also implements some of the most popular Flake8 plugins natively, including - [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) - [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-print`](https://pypi.org/project/flake8-print/) -- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (14/16) +- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (15/16) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32) - [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (37/48) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34) @@ -294,6 +294,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com | C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary literal passed to tuple() - remove the outer call to tuple() | | | | C410 | UnnecessaryLiteralWithinListCall | Unnecessary literal passed to list() - rewrite as a list literal | | | | C411 | UnnecessaryListCall | Unnecessary list call - remove the outer call to list() | | | +| C413 | UnnecessaryCallAroundSorted | Unnecessary call around sorted() | | | | C414 | UnnecessaryDoubleCastOrProcess | Unnecessary call within (). | | | | C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within () | | | | C416 | UnnecessaryComprehension | Unnecessary comprehension - rewrite using () | | | diff --git a/resources/test/fixtures/C413.py b/resources/test/fixtures/C413.py new file mode 100644 index 0000000000..27736b9836 --- /dev/null +++ b/resources/test/fixtures/C413.py @@ -0,0 +1,4 @@ +x = [2, 3, 1] +list(sorted(x)) +reversed(sorted(x)) +reversed(sorted(x, reverse=True)) diff --git a/src/ast/checkers.rs b/src/ast/checkers.rs index fd753d3177..15d3ecdcfa 100644 --- a/src/ast/checkers.rs +++ b/src/ast/checkers.rs @@ -1020,6 +1020,26 @@ pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option< None } +pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -> Option { + if let ExprKind::Name { id: outer, .. } = &func.node { + if outer == "list" || outer == "reversed" { + if let Some(arg) = args.first() { + if let ExprKind::Call { func, .. } = &arg.node { + if let ExprKind::Name { id: inner, .. } = &func.node { + if inner == "sorted" { + return Some(Check::new( + CheckKind::UnnecessaryCallAroundSorted(outer.to_string()), + Range::from_located(expr), + )); + } + } + } + } + } + } + None +} + pub fn unnecessary_double_cast_or_process( expr: &Expr, func: &Expr, diff --git a/src/check_ast.rs b/src/check_ast.rs index 5f5bff98fb..7b1522b80e 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -843,6 +843,13 @@ where }; } + if self.settings.enabled.contains(&CheckCode::C413) { + if let Some(check) = checkers::unnecessary_call_around_sorted(expr, func, args) + { + self.checks.push(check); + }; + } + if self.settings.enabled.contains(&CheckCode::C414) { if let Some(check) = checkers::unnecessary_double_cast_or_process(expr, func, args) diff --git a/src/checks.rs b/src/checks.rs index 4775f3ea10..19d4718655 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -138,6 +138,7 @@ pub enum CheckCode { C409, C410, C411, + C413, C414, C415, C416, @@ -275,6 +276,7 @@ pub enum CheckKind { UnnecessaryLiteralWithinTupleCall(String), UnnecessaryLiteralWithinListCall(String), UnnecessaryListCall, + UnnecessaryCallAroundSorted(String), UnnecessaryDoubleCastOrProcess(String, String), UnnecessarySubscriptReversal(String), UnnecessaryComprehension(String), @@ -419,6 +421,9 @@ impl CheckCode { CheckKind::UnnecessaryLiteralWithinListCall("".to_string()) } CheckCode::C411 => CheckKind::UnnecessaryListCall, + CheckCode::C413 => { + CheckKind::UnnecessaryCallAroundSorted("".to_string()) + } CheckCode::C414 => CheckKind::UnnecessaryDoubleCastOrProcess( "".to_string(), "".to_string(), @@ -559,6 +564,7 @@ impl CheckKind { CheckKind::UnnecessaryLiteralWithinTupleCall(..) => &CheckCode::C409, CheckKind::UnnecessaryLiteralWithinListCall(..) => &CheckCode::C410, CheckKind::UnnecessaryListCall => &CheckCode::C411, + CheckKind::UnnecessaryCallAroundSorted(_) => &CheckCode::C413, CheckKind::UnnecessaryDoubleCastOrProcess(..) => &CheckCode::C414, CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415, CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416, @@ -827,6 +833,9 @@ impl CheckKind { CheckKind::UnnecessaryListCall => { "Unnecessary list call - remove the outer call to list()".to_string() } + CheckKind::UnnecessaryCallAroundSorted(func) => { + format!("Unnecessary {func} call around sorted()") + } CheckKind::UnnecessaryDoubleCastOrProcess(inner, outer) => { format!("Unnecessary {inner} call within {outer}().") } diff --git a/src/linter.rs b/src/linter.rs index 4c4f1ec6c0..37bfd7af24 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -1019,6 +1019,18 @@ mod tests { Ok(()) } + #[test] + fn c413() -> Result<()> { + let mut checks = check_path( + Path::new("./resources/test/fixtures/C413.py"), + &settings::Settings::for_rule(CheckCode::C413), + &fixer::Mode::Generate, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(checks); + Ok(()) + } + #[test] fn c414() -> Result<()> { let mut checks = check_path( diff --git a/src/snapshots/ruff__linter__tests__c413.snap b/src/snapshots/ruff__linter__tests__c413.snap new file mode 100644 index 0000000000..cbaf1d8547 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__c413.snap @@ -0,0 +1,32 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + UnnecessaryCallAroundSorted: list + location: + row: 2 + column: 1 + end_location: + row: 2 + column: 16 + fix: ~ +- kind: + UnnecessaryCallAroundSorted: reversed + location: + row: 3 + column: 1 + end_location: + row: 3 + column: 20 + fix: ~ +- kind: + UnnecessaryCallAroundSorted: reversed + location: + row: 4 + column: 1 + end_location: + row: 4 + column: 34 + fix: ~ +