diff --git a/src/registry.rs b/src/registry.rs index aee7fd8995..a3ddcbf338 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -130,22 +130,22 @@ ruff_macros::define_rule_mapping!( // flake8-blind-except BLE001 => violations::BlindExcept, // flake8-comprehensions - C400 => violations::UnnecessaryGeneratorList, - C401 => violations::UnnecessaryGeneratorSet, - C402 => violations::UnnecessaryGeneratorDict, - C403 => violations::UnnecessaryListComprehensionSet, - C404 => violations::UnnecessaryListComprehensionDict, - C405 => violations::UnnecessaryLiteralSet, - C406 => violations::UnnecessaryLiteralDict, - C408 => violations::UnnecessaryCollectionCall, - C409 => violations::UnnecessaryLiteralWithinTupleCall, - C410 => violations::UnnecessaryLiteralWithinListCall, - C411 => violations::UnnecessaryListCall, - C413 => violations::UnnecessaryCallAroundSorted, - C414 => violations::UnnecessaryDoubleCastOrProcess, - C415 => violations::UnnecessarySubscriptReversal, - C416 => violations::UnnecessaryComprehension, - C417 => violations::UnnecessaryMap, + C400 => rules::flake8_comprehensions::rules::UnnecessaryGeneratorList, + C401 => rules::flake8_comprehensions::rules::UnnecessaryGeneratorSet, + C402 => rules::flake8_comprehensions::rules::UnnecessaryGeneratorDict, + C403 => rules::flake8_comprehensions::rules::UnnecessaryListComprehensionSet, + C404 => rules::flake8_comprehensions::rules::UnnecessaryListComprehensionDict, + C405 => rules::flake8_comprehensions::rules::UnnecessaryLiteralSet, + C406 => rules::flake8_comprehensions::rules::UnnecessaryLiteralDict, + C408 => rules::flake8_comprehensions::rules::UnnecessaryCollectionCall, + C409 => rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinTupleCall, + C410 => rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinListCall, + C411 => rules::flake8_comprehensions::rules::UnnecessaryListCall, + C413 => rules::flake8_comprehensions::rules::UnnecessaryCallAroundSorted, + C414 => rules::flake8_comprehensions::rules::UnnecessaryDoubleCastOrProcess, + C415 => rules::flake8_comprehensions::rules::UnnecessarySubscriptReversal, + C416 => rules::flake8_comprehensions::rules::UnnecessaryComprehension, + C417 => rules::flake8_comprehensions::rules::UnnecessaryMap, // flake8-debugger T100 => violations::Debugger, // mccabe diff --git a/src/rules/flake8_comprehensions/rules.rs b/src/rules/flake8_comprehensions/rules.rs deleted file mode 100644 index a46656144e..0000000000 --- a/src/rules/flake8_comprehensions/rules.rs +++ /dev/null @@ -1,728 +0,0 @@ -use log::error; -use num_bigint::BigInt; -use rustpython_ast::{Comprehension, Constant, Expr, ExprKind, Keyword, Unaryop}; - -use super::fixes; -use crate::ast::types::Range; -use crate::checkers::ast::Checker; -use crate::registry::Diagnostic; -use crate::violations; - -fn function_name(func: &Expr) -> Option<&str> { - if let ExprKind::Name { id, .. } = &func.node { - Some(id) - } else { - None - } -} - -fn exactly_one_argument_with_matching_function<'a>( - name: &str, - func: &Expr, - args: &'a [Expr], - keywords: &[Keyword], -) -> Option<&'a ExprKind> { - if !keywords.is_empty() { - return None; - } - if args.len() != 1 { - return None; - } - if function_name(func)? != name { - return None; - } - Some(&args[0].node) -} - -fn first_argument_with_matching_function<'a>( - name: &str, - func: &Expr, - args: &'a [Expr], -) -> Option<&'a ExprKind> { - if function_name(func)? == name { - Some(&args.first()?.node) - } else { - None - } -} - -/// C400 (`list(generator)`) -pub fn unnecessary_generator_list( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("list", func, args, keywords) else { - return; - }; - if !checker.is_builtin("list") { - return; - } - if let ExprKind::GeneratorExp { .. } = argument { - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryGeneratorList, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_generator_list(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); - } -} - -/// C401 (`set(generator)`) -pub fn unnecessary_generator_set( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("set", func, args, keywords) else { - return; - }; - if !checker.is_builtin("set") { - return; - } - if let ExprKind::GeneratorExp { .. } = argument { - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryGeneratorSet, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_generator_set(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); - } -} - -/// C402 (`dict((x, y) for x, y in iterable)`) -pub fn unnecessary_generator_dict( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("dict", func, args, keywords) else { - return; - }; - if let ExprKind::GeneratorExp { elt, .. } = argument { - match &elt.node { - ExprKind::Tuple { elts, .. } if elts.len() == 2 => { - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryGeneratorDict, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_generator_dict( - checker.locator, - checker.stylist, - expr, - ) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); - } - _ => {} - } - } -} - -/// C403 (`set([...])`) -pub fn unnecessary_list_comprehension_set( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("set", func, args, keywords) else { - return; - }; - if !checker.is_builtin("set") { - return; - } - if let ExprKind::ListComp { .. } = &argument { - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryListComprehensionSet, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_list_comprehension_set( - checker.locator, - checker.stylist, - expr, - ) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); - } -} - -/// C404 (`dict([...])`) -pub fn unnecessary_list_comprehension_dict( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("dict", func, args, keywords) else { - return; - }; - if !checker.is_builtin("dict") { - return; - } - let ExprKind::ListComp { elt, .. } = &argument else { - return; - }; - let ExprKind::Tuple { elts, .. } = &elt.node else { - return; - }; - if elts.len() != 2 { - return; - } - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryListComprehensionDict, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_list_comprehension_dict(checker.locator, checker.stylist, expr) - { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C405 (`set([1, 2])`) -pub fn unnecessary_literal_set( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("set", func, args, keywords) else { - return; - }; - if !checker.is_builtin("set") { - return; - } - let kind = match argument { - ExprKind::List { .. } => "list", - ExprKind::Tuple { .. } => "tuple", - _ => return, - }; - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryLiteralSet { - obj_type: kind.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_literal_set(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C406 (`dict([(1, 2)])`) -pub fn unnecessary_literal_dict( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Some(argument) = exactly_one_argument_with_matching_function("dict", func, args, keywords) else { - return; - }; - if !checker.is_builtin("dict") { - return; - } - let (kind, elts) = match argument { - ExprKind::Tuple { elts, .. } => ("tuple", elts), - ExprKind::List { elts, .. } => ("list", elts), - _ => return, - }; - // Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`. - if !elts - .iter() - .all(|elt| matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2)) - { - return; - } - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryLiteralDict { - obj_type: kind.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_literal_dict(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C408 -pub fn unnecessary_collection_call( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - if !args.is_empty() { - return; - } - let Some(id) = function_name(func) else { - return; - }; - match id { - "dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => { - // `dict()` or `dict(a=1)` (as opposed to `dict(**a)`) - } - "list" | "tuple" => { - // `list()` or `tuple()` - } - _ => return, - }; - if !checker.is_builtin(id) { - return; - } - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryCollectionCall { - obj_type: id.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_collection_call(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C409 -pub fn unnecessary_literal_within_tuple_call( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], -) { - let Some(argument) = first_argument_with_matching_function("tuple", func, args) else { - return; - }; - if !checker.is_builtin("tuple") { - return; - } - let argument_kind = match argument { - ExprKind::Tuple { .. } => "tuple", - ExprKind::List { .. } => "list", - _ => return, - }; - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryLiteralWithinTupleCall { - literal: argument_kind.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_literal_within_tuple_call( - checker.locator, - checker.stylist, - expr, - ) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C410 -pub fn unnecessary_literal_within_list_call( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], -) { - let Some(argument) = first_argument_with_matching_function("list", func, args) else { - return; - }; - if !checker.is_builtin("list") { - return; - } - let argument_kind = match argument { - ExprKind::Tuple { .. } => "tuple", - ExprKind::List { .. } => "list", - _ => return, - }; - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryLiteralWithinListCall { - literal: argument_kind.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_literal_within_list_call( - checker.locator, - checker.stylist, - expr, - ) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C411 -pub fn unnecessary_list_call(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { - let Some(argument) = first_argument_with_matching_function("list", func, args) else { - return; - }; - if !checker.is_builtin("list") { - return; - } - if !matches!(argument, ExprKind::ListComp { .. }) { - return; - } - let mut diagnostic = - Diagnostic::new(violations::UnnecessaryListCall, Range::from_located(expr)); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_list_call(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C413 -pub fn unnecessary_call_around_sorted( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], -) { - let Some(outer) = function_name(func) else { - return; - }; - if !(outer == "list" || outer == "reversed") { - return; - } - let Some(arg) = args.first() else { - return; - }; - let ExprKind::Call { func, .. } = &arg.node else { - return; - }; - let Some(inner) = function_name(func) else { - return; - }; - if inner != "sorted" { - return; - } - if !checker.is_builtin(inner) || !checker.is_builtin(outer) { - return; - } - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryCallAroundSorted { - func: outer.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_call_around_sorted(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C414 -pub fn unnecessary_double_cast_or_process( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], -) { - fn diagnostic(inner: &str, outer: &str, location: Range) -> Diagnostic { - Diagnostic::new( - violations::UnnecessaryDoubleCastOrProcess { - inner: inner.to_string(), - outer: outer.to_string(), - }, - location, - ) - } - - let Some(outer) = function_name(func) else { - return; - }; - if !(outer == "list" - || outer == "tuple" - || outer == "set" - || outer == "reversed" - || outer == "sorted") - { - return; - } - let Some(arg) = args.first() else { - return; - }; - let ExprKind::Call { func, .. } = &arg.node else { - return; - }; - let Some(inner) = function_name(func) else { - return; - }; - if !checker.is_builtin(inner) || !checker.is_builtin(outer) { - return; - } - - // Ex) set(tuple(...)) - if (outer == "set" || outer == "sorted") - && (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted") - { - checker - .diagnostics - .push(diagnostic(inner, outer, Range::from_located(expr))); - return; - } - - // Ex) list(tuple(...)) - if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") { - checker - .diagnostics - .push(diagnostic(inner, outer, Range::from_located(expr))); - return; - } - - // Ex) set(set(...)) - if outer == "set" && inner == "set" { - checker - .diagnostics - .push(diagnostic(inner, outer, Range::from_located(expr))); - } -} - -/// C415 -pub fn unnecessary_subscript_reversal( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], -) { - let Some(first_arg) = args.first() else { - return; - }; - let Some(id) = function_name(func) else { - return; - }; - if !(id == "set" || id == "sorted" || id == "reversed") { - return; - } - if !checker.is_builtin(id) { - return; - } - let ExprKind::Subscript { slice, .. } = &first_arg.node else { - return; - }; - let ExprKind::Slice { lower, upper, step } = &slice.node else { - return; - }; - if lower.is_some() || upper.is_some() { - return; - } - let Some(step) = step.as_ref() else { - return; - }; - let ExprKind::UnaryOp { - op: Unaryop::USub, - operand, - } = &step.node else { - return; - }; - let ExprKind::Constant { - value: Constant::Int(val), - .. - } = &operand.node else { - return; - }; - if *val != BigInt::from(1) { - return; - }; - checker.diagnostics.push(Diagnostic::new( - violations::UnnecessarySubscriptReversal { - func: id.to_string(), - }, - Range::from_located(expr), - )); -} - -/// C416 -pub fn unnecessary_comprehension( - checker: &mut Checker, - expr: &Expr, - elt: &Expr, - generators: &[Comprehension], -) { - if generators.len() != 1 { - return; - } - let generator = &generators[0]; - if !(generator.ifs.is_empty() && generator.is_async == 0) { - return; - } - let Some(elt_id) = function_name(elt) else { - return; - }; - - let Some(target_id) = function_name(&generator.target) else { - return; - }; - if elt_id != target_id { - return; - } - let id = match &expr.node { - ExprKind::ListComp { .. } => "list", - ExprKind::SetComp { .. } => "set", - _ => return, - }; - if !checker.is_builtin(id) { - return; - } - let mut diagnostic = Diagnostic::new( - violations::UnnecessaryComprehension { - obj_type: id.to_string(), - }, - Range::from_located(expr), - ); - if checker.patch(diagnostic.kind.rule()) { - match fixes::fix_unnecessary_comprehension(checker.locator, checker.stylist, expr) { - Ok(fix) => { - diagnostic.amend(fix); - } - Err(e) => error!("Failed to generate fix: {e}"), - } - } - checker.diagnostics.push(diagnostic); -} - -/// C417 -pub fn unnecessary_map(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { - fn diagnostic(kind: &str, location: Range) -> Diagnostic { - Diagnostic::new( - violations::UnnecessaryMap { - obj_type: kind.to_string(), - }, - location, - ) - } - - let Some(id) = function_name(func) else { - return; - }; - match id { - "map" => { - if !checker.is_builtin(id) { - return; - } - - if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) { - checker - .diagnostics - .push(diagnostic("generator", Range::from_located(expr))); - } - } - "list" | "set" => { - if !checker.is_builtin(id) { - return; - } - - if let Some(arg) = args.first() { - if let ExprKind::Call { func, args, .. } = &arg.node { - let Some(argument) = first_argument_with_matching_function("map", func, args) else { - return; - }; - if let ExprKind::Lambda { .. } = argument { - checker - .diagnostics - .push(diagnostic(id, Range::from_located(expr))); - } - } - } - } - "dict" => { - if !checker.is_builtin(id) { - return; - } - - if args.len() == 1 { - if let ExprKind::Call { func, args, .. } = &args[0].node { - let Some(argument) = first_argument_with_matching_function("map", func, args) else { - return; - }; - if let ExprKind::Lambda { body, .. } = &argument { - if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2) - { - checker - .diagnostics - .push(diagnostic(id, Range::from_located(expr))); - } - } - } - } - } - _ => (), - } -} diff --git a/src/rules/flake8_comprehensions/rules/helpers.rs b/src/rules/flake8_comprehensions/rules/helpers.rs new file mode 100644 index 0000000000..20ec40ddc4 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/helpers.rs @@ -0,0 +1,39 @@ +use rustpython_ast::{Expr, ExprKind, Keyword}; + +pub fn function_name(func: &Expr) -> Option<&str> { + if let ExprKind::Name { id, .. } = &func.node { + Some(id) + } else { + None + } +} + +pub fn exactly_one_argument_with_matching_function<'a>( + name: &str, + func: &Expr, + args: &'a [Expr], + keywords: &[Keyword], +) -> Option<&'a ExprKind> { + if !keywords.is_empty() { + return None; + } + if args.len() != 1 { + return None; + } + if function_name(func)? != name { + return None; + } + Some(&args[0].node) +} + +pub fn first_argument_with_matching_function<'a>( + name: &str, + func: &Expr, + args: &'a [Expr], +) -> Option<&'a ExprKind> { + if function_name(func)? == name { + Some(&args.first()?.node) + } else { + None + } +} diff --git a/src/rules/flake8_comprehensions/rules/mod.rs b/src/rules/flake8_comprehensions/rules/mod.rs new file mode 100644 index 0000000000..6051a961c9 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/mod.rs @@ -0,0 +1,48 @@ +mod helpers; +mod unnecessary_call_around_sorted; +mod unnecessary_collection_call; +mod unnecessary_comprehension; +mod unnecessary_double_cast_or_process; +mod unnecessary_generator_dict; +mod unnecessary_generator_list; +mod unnecessary_generator_set; +mod unnecessary_list_call; +mod unnecessary_list_comprehension_dict; +mod unnecessary_list_comprehension_set; +mod unnecessary_literal_dict; +mod unnecessary_literal_set; +mod unnecessary_literal_within_list_call; +mod unnecessary_literal_within_tuple_call; +mod unnecessary_map; +mod unnecessary_subscript_reversal; + +pub use unnecessary_call_around_sorted::{ + unnecessary_call_around_sorted, UnnecessaryCallAroundSorted, +}; +pub use unnecessary_collection_call::{unnecessary_collection_call, UnnecessaryCollectionCall}; +pub use unnecessary_comprehension::{unnecessary_comprehension, UnnecessaryComprehension}; +pub use unnecessary_double_cast_or_process::{ + unnecessary_double_cast_or_process, UnnecessaryDoubleCastOrProcess, +}; +pub use unnecessary_generator_dict::{unnecessary_generator_dict, UnnecessaryGeneratorDict}; +pub use unnecessary_generator_list::{unnecessary_generator_list, UnnecessaryGeneratorList}; +pub use unnecessary_generator_set::{unnecessary_generator_set, UnnecessaryGeneratorSet}; +pub use unnecessary_list_call::{unnecessary_list_call, UnnecessaryListCall}; +pub use unnecessary_list_comprehension_dict::{ + unnecessary_list_comprehension_dict, UnnecessaryListComprehensionDict, +}; +pub use unnecessary_list_comprehension_set::{ + unnecessary_list_comprehension_set, UnnecessaryListComprehensionSet, +}; +pub use unnecessary_literal_dict::{unnecessary_literal_dict, UnnecessaryLiteralDict}; +pub use unnecessary_literal_set::{unnecessary_literal_set, UnnecessaryLiteralSet}; +pub use unnecessary_literal_within_list_call::{ + unnecessary_literal_within_list_call, UnnecessaryLiteralWithinListCall, +}; +pub use unnecessary_literal_within_tuple_call::{ + unnecessary_literal_within_tuple_call, UnnecessaryLiteralWithinTupleCall, +}; +pub use unnecessary_map::{unnecessary_map, UnnecessaryMap}; +pub use unnecessary_subscript_reversal::{ + unnecessary_subscript_reversal, UnnecessarySubscriptReversal, +}; diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs b/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs new file mode 100644 index 0000000000..155d8f8054 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_call_around_sorted.rs @@ -0,0 +1,73 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryCallAroundSorted { + pub func: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryCallAroundSorted { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryCallAroundSorted { func } = self; + format!("Unnecessary `{func}` call around `sorted()`") + } + + fn autofix_title(&self) -> String { + let UnnecessaryCallAroundSorted { func } = self; + format!("Remove unnecessary `{func}` call") + } +} + +/// C413 +pub fn unnecessary_call_around_sorted( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], +) { + let Some(outer) = helpers::function_name(func) else { + return; + }; + if !(outer == "list" || outer == "reversed") { + return; + } + let Some(arg) = args.first() else { + return; + }; + let ExprKind::Call { func, .. } = &arg.node else { + return; + }; + let Some(inner) = helpers::function_name(func) else { + return; + }; + if inner != "sorted" { + return; + } + if !checker.is_builtin(inner) || !checker.is_builtin(outer) { + return; + } + let mut diagnostic = Diagnostic::new( + UnnecessaryCallAroundSorted { + func: outer.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_call_around_sorted(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs b/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs new file mode 100644 index 0000000000..bf768e3c4a --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_collection_call.rs @@ -0,0 +1,70 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, Keyword}; + +define_violation!( + pub struct UnnecessaryCollectionCall { + pub obj_type: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryCollectionCall { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryCollectionCall { obj_type } = self; + format!("Unnecessary `{obj_type}` call (rewrite as a literal)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a literal".to_string() + } +} + +/// C408 +pub fn unnecessary_collection_call( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + if !args.is_empty() { + return; + } + let Some(id) = helpers::function_name(func) else { + return; + }; + match id { + "dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => { + // `dict()` or `dict(a=1)` (as opposed to `dict(**a)`) + } + "list" | "tuple" => { + // `list()` or `tuple()` + } + _ => return, + }; + if !checker.is_builtin(id) { + return; + } + let mut diagnostic = Diagnostic::new( + UnnecessaryCollectionCall { + obj_type: id.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_collection_call(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs b/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs new file mode 100644 index 0000000000..c349d96308 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_comprehension.rs @@ -0,0 +1,77 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Comprehension, Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryComprehension { + pub obj_type: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryComprehension { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryComprehension { obj_type } = self; + format!("Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`)") + } + + fn autofix_title(&self) -> String { + let UnnecessaryComprehension { obj_type } = self; + format!("Rewrite using `{obj_type}()`") + } +} + +/// C416 +pub fn unnecessary_comprehension( + checker: &mut Checker, + expr: &Expr, + elt: &Expr, + generators: &[Comprehension], +) { + if generators.len() != 1 { + return; + } + let generator = &generators[0]; + if !(generator.ifs.is_empty() && generator.is_async == 0) { + return; + } + let Some(elt_id) = helpers::function_name(elt) else { + return; + }; + + let Some(target_id) = helpers::function_name(&generator.target) else { + return; + }; + if elt_id != target_id { + return; + } + let id = match &expr.node { + ExprKind::ListComp { .. } => "list", + ExprKind::SetComp { .. } => "set", + _ => return, + }; + if !checker.is_builtin(id) { + return; + } + let mut diagnostic = Diagnostic::new( + UnnecessaryComprehension { + obj_type: id.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_comprehension(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs b/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs new file mode 100644 index 0000000000..69df3e402b --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_double_cast_or_process.rs @@ -0,0 +1,90 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; + +use crate::violation::Violation; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryDoubleCastOrProcess { + pub inner: String, + pub outer: String, + } +); +impl Violation for UnnecessaryDoubleCastOrProcess { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryDoubleCastOrProcess { inner, outer } = self; + format!("Unnecessary `{inner}` call within `{outer}()`") + } +} + +/// C414 +pub fn unnecessary_double_cast_or_process( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], +) { + fn diagnostic(inner: &str, outer: &str, location: Range) -> Diagnostic { + Diagnostic::new( + UnnecessaryDoubleCastOrProcess { + inner: inner.to_string(), + outer: outer.to_string(), + }, + location, + ) + } + + let Some(outer) = helpers::function_name(func) else { + return; + }; + if !(outer == "list" + || outer == "tuple" + || outer == "set" + || outer == "reversed" + || outer == "sorted") + { + return; + } + let Some(arg) = args.first() else { + return; + }; + let ExprKind::Call { func, .. } = &arg.node else { + return; + }; + let Some(inner) = helpers::function_name(func) else { + return; + }; + if !checker.is_builtin(inner) || !checker.is_builtin(outer) { + return; + } + + // Ex) set(tuple(...)) + if (outer == "set" || outer == "sorted") + && (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted") + { + checker + .diagnostics + .push(diagnostic(inner, outer, Range::from_located(expr))); + return; + } + + // Ex) list(tuple(...)) + if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") { + checker + .diagnostics + .push(diagnostic(inner, outer, Range::from_located(expr))); + return; + } + + // Ex) set(set(...)) + if outer == "set" && inner == "set" { + checker + .diagnostics + .push(diagnostic(inner, outer, Range::from_located(expr))); + } +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs b/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs new file mode 100644 index 0000000000..6faba9ce86 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_generator_dict.rs @@ -0,0 +1,59 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryGeneratorDict; +); +impl AlwaysAutofixableViolation for UnnecessaryGeneratorDict { + #[derive_message_formats] + fn message(&self) -> String { + format!("Unnecessary generator (rewrite as a `dict` comprehension)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `dict` comprehension".to_string() + } +} + +/// C402 (`dict((x, y) for x, y in iterable)`) +pub fn unnecessary_generator_dict( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else { + return; + }; + if let ExprKind::GeneratorExp { elt, .. } = argument { + match &elt.node { + ExprKind::Tuple { elts, .. } if elts.len() == 2 => { + let mut diagnostic = + Diagnostic::new(UnnecessaryGeneratorDict, Range::from_located(expr)); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_generator_dict( + checker.locator, + checker.stylist, + expr, + ) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); + } + _ => {} + } + } +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs b/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs new file mode 100644 index 0000000000..b27ddca12e --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_generator_list.rs @@ -0,0 +1,52 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryGeneratorList; +); +impl AlwaysAutofixableViolation for UnnecessaryGeneratorList { + #[derive_message_formats] + fn message(&self) -> String { + format!("Unnecessary generator (rewrite as a `list` comprehension)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `list` comprehension".to_string() + } +} + +/// C400 (`list(generator)`) +pub fn unnecessary_generator_list( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("list", func, args, keywords) else { + return; + }; + if !checker.is_builtin("list") { + return; + } + if let ExprKind::GeneratorExp { .. } = argument { + let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, Range::from_located(expr)); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_generator_list(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); + } +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs b/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs new file mode 100644 index 0000000000..632122563f --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_generator_set.rs @@ -0,0 +1,52 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryGeneratorSet; +); +impl AlwaysAutofixableViolation for UnnecessaryGeneratorSet { + #[derive_message_formats] + fn message(&self) -> String { + format!("Unnecessary generator (rewrite as a `set` comprehension)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `set` comprehension".to_string() + } +} + +/// C401 (`set(generator)`) +pub fn unnecessary_generator_set( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else { + return; + }; + if !checker.is_builtin("set") { + return; + } + if let ExprKind::GeneratorExp { .. } = argument { + let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, Range::from_located(expr)); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_generator_set(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); + } +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs b/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs new file mode 100644 index 0000000000..86c55c99f5 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_list_call.rs @@ -0,0 +1,47 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryListCall; +); +impl AlwaysAutofixableViolation for UnnecessaryListCall { + #[derive_message_formats] + fn message(&self) -> String { + format!("Unnecessary `list` call (remove the outer call to `list()`)") + } + + fn autofix_title(&self) -> String { + "Remove outer `list` call".to_string() + } +} + +/// C411 +pub fn unnecessary_list_call(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { + let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else { + return; + }; + if !checker.is_builtin("list") { + return; + } + if !matches!(argument, ExprKind::ListComp { .. }) { + return; + } + let mut diagnostic = Diagnostic::new(UnnecessaryListCall, Range::from_located(expr)); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_list_call(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs b/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs new file mode 100644 index 0000000000..97938daaf0 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_dict.rs @@ -0,0 +1,61 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryListComprehensionDict; +); +impl AlwaysAutofixableViolation for UnnecessaryListComprehensionDict { + #[derive_message_formats] + fn message(&self) -> String { + format!("Unnecessary `list` comprehension (rewrite as a `dict` comprehension)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `dict` comprehension".to_string() + } +} + +/// C404 (`dict([...])`) +pub fn unnecessary_list_comprehension_dict( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else { + return; + }; + if !checker.is_builtin("dict") { + return; + } + let ExprKind::ListComp { elt, .. } = &argument else { + return; + }; + let ExprKind::Tuple { elts, .. } = &elt.node else { + return; + }; + if elts.len() != 2 { + return; + } + let mut diagnostic = + Diagnostic::new(UnnecessaryListComprehensionDict, Range::from_located(expr)); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_list_comprehension_dict(checker.locator, checker.stylist, expr) + { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs b/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs new file mode 100644 index 0000000000..177bc9e37c --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_list_comprehension_set.rs @@ -0,0 +1,57 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryListComprehensionSet; +); +impl AlwaysAutofixableViolation for UnnecessaryListComprehensionSet { + #[derive_message_formats] + fn message(&self) -> String { + format!("Unnecessary `list` comprehension (rewrite as a `set` comprehension)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `set` comprehension".to_string() + } +} + +/// C403 (`set([...])`) +pub fn unnecessary_list_comprehension_set( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else { + return; + }; + if !checker.is_builtin("set") { + return; + } + if let ExprKind::ListComp { .. } = &argument { + let mut diagnostic = + Diagnostic::new(UnnecessaryListComprehensionSet, Range::from_located(expr)); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_list_comprehension_set( + checker.locator, + checker.stylist, + expr, + ) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); + } +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs b/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs new file mode 100644 index 0000000000..b49e999f93 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_literal_dict.rs @@ -0,0 +1,70 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryLiteralDict { + pub obj_type: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryLiteralDict { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryLiteralDict { obj_type } = self; + format!("Unnecessary `{obj_type}` literal (rewrite as a `dict` literal)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `dict` literal".to_string() + } +} + +/// C406 (`dict([(1, 2)])`) +pub fn unnecessary_literal_dict( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else { + return; + }; + if !checker.is_builtin("dict") { + return; + } + let (kind, elts) = match argument { + ExprKind::Tuple { elts, .. } => ("tuple", elts), + ExprKind::List { elts, .. } => ("list", elts), + _ => return, + }; + // Accept `dict((1, 2), ...))` `dict([(1, 2), ...])`. + if !elts + .iter() + .all(|elt| matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2)) + { + return; + } + let mut diagnostic = Diagnostic::new( + UnnecessaryLiteralDict { + obj_type: kind.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_literal_dict(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs b/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs new file mode 100644 index 0000000000..0ec8129989 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_literal_set.rs @@ -0,0 +1,63 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind, Keyword}; + +define_violation!( + pub struct UnnecessaryLiteralSet { + pub obj_type: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryLiteralSet { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryLiteralSet { obj_type } = self; + format!("Unnecessary `{obj_type}` literal (rewrite as a `set` literal)") + } + + fn autofix_title(&self) -> String { + "Rewrite as a `set` literal".to_string() + } +} + +/// C405 (`set([1, 2])`) +pub fn unnecessary_literal_set( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { + let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else { + return; + }; + if !checker.is_builtin("set") { + return; + } + let kind = match argument { + ExprKind::List { .. } => "list", + ExprKind::Tuple { .. } => "tuple", + _ => return, + }; + let mut diagnostic = Diagnostic::new( + UnnecessaryLiteralSet { + obj_type: kind.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_literal_set(checker.locator, checker.stylist, expr) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs b/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs new file mode 100644 index 0000000000..44ba2b63ab --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_list_call.rs @@ -0,0 +1,82 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryLiteralWithinListCall { + pub literal: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryLiteralWithinListCall { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryLiteralWithinListCall { literal } = self; + if literal == "list" { + format!( + "Unnecessary `{literal}` literal passed to `list()` (remove the outer call to \ + `list()`)" + ) + } else { + format!( + "Unnecessary `{literal}` literal passed to `list()` (rewrite as a `list` literal)" + ) + } + } + + fn autofix_title(&self) -> String { + let UnnecessaryLiteralWithinListCall { literal } = self; + { + if literal == "list" { + "Remove outer `list` call".to_string() + } else { + "Rewrite as a `list` literal".to_string() + } + } + } +} + +/// C410 +pub fn unnecessary_literal_within_list_call( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], +) { + let Some(argument) = helpers::first_argument_with_matching_function("list", func, args) else { + return; + }; + if !checker.is_builtin("list") { + return; + } + let argument_kind = match argument { + ExprKind::Tuple { .. } => "tuple", + ExprKind::List { .. } => "list", + _ => return, + }; + let mut diagnostic = Diagnostic::new( + UnnecessaryLiteralWithinListCall { + literal: argument_kind.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_literal_within_list_call( + checker.locator, + checker.stylist, + expr, + ) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs b/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs new file mode 100644 index 0000000000..c90322e671 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_literal_within_tuple_call.rs @@ -0,0 +1,83 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; +use crate::rules::flake8_comprehensions::fixes; +use crate::violation::AlwaysAutofixableViolation; +use log::error; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryLiteralWithinTupleCall { + pub literal: String, + } +); +impl AlwaysAutofixableViolation for UnnecessaryLiteralWithinTupleCall { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryLiteralWithinTupleCall { literal } = self; + if literal == "list" { + format!( + "Unnecessary `{literal}` literal passed to `tuple()` (rewrite as a `tuple` \ + literal)" + ) + } else { + format!( + "Unnecessary `{literal}` literal passed to `tuple()` (remove the outer call to \ + `tuple()`)" + ) + } + } + + fn autofix_title(&self) -> String { + let UnnecessaryLiteralWithinTupleCall { literal } = self; + { + if literal == "list" { + "Rewrite as a `tuple` literal".to_string() + } else { + "Remove outer `tuple` call".to_string() + } + } + } +} + +/// C409 +pub fn unnecessary_literal_within_tuple_call( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], +) { + let Some(argument) = helpers::first_argument_with_matching_function("tuple", func, args) else { + return; + }; + if !checker.is_builtin("tuple") { + return; + } + let argument_kind = match argument { + ExprKind::Tuple { .. } => "tuple", + ExprKind::List { .. } => "list", + _ => return, + }; + let mut diagnostic = Diagnostic::new( + UnnecessaryLiteralWithinTupleCall { + literal: argument_kind.to_string(), + }, + Range::from_located(expr), + ); + if checker.patch(diagnostic.kind.rule()) { + match fixes::fix_unnecessary_literal_within_tuple_call( + checker.locator, + checker.stylist, + expr, + ) { + Ok(fix) => { + diagnostic.amend(fix); + } + Err(e) => error!("Failed to generate fix: {e}"), + } + } + checker.diagnostics.push(diagnostic); +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_map.rs b/src/rules/flake8_comprehensions/rules/unnecessary_map.rs new file mode 100644 index 0000000000..47c6e5f2fd --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_map.rs @@ -0,0 +1,95 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; + +use crate::violation::Violation; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Expr, ExprKind}; + +define_violation!( + pub struct UnnecessaryMap { + pub obj_type: String, + } +); +impl Violation for UnnecessaryMap { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessaryMap { obj_type } = self; + if obj_type == "generator" { + format!("Unnecessary `map` usage (rewrite using a generator expression)") + } else { + format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)") + } + } +} + +/// C417 +pub fn unnecessary_map(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { + fn diagnostic(kind: &str, location: Range) -> Diagnostic { + Diagnostic::new( + UnnecessaryMap { + obj_type: kind.to_string(), + }, + location, + ) + } + + let Some(id) = helpers::function_name(func) else { + return; + }; + match id { + "map" => { + if !checker.is_builtin(id) { + return; + } + + if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) { + checker + .diagnostics + .push(diagnostic("generator", Range::from_located(expr))); + } + } + "list" | "set" => { + if !checker.is_builtin(id) { + return; + } + + if let Some(arg) = args.first() { + if let ExprKind::Call { func, args, .. } = &arg.node { + let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else { + return; + }; + if let ExprKind::Lambda { .. } = argument { + checker + .diagnostics + .push(diagnostic(id, Range::from_located(expr))); + } + } + } + } + "dict" => { + if !checker.is_builtin(id) { + return; + } + + if args.len() == 1 { + if let ExprKind::Call { func, args, .. } = &args[0].node { + let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else { + return; + }; + if let ExprKind::Lambda { body, .. } = &argument { + if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2) + { + checker + .diagnostics + .push(diagnostic(id, Range::from_located(expr))); + } + } + } + } + } + _ => (), + } +} diff --git a/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs b/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs new file mode 100644 index 0000000000..a5658aa967 --- /dev/null +++ b/src/rules/flake8_comprehensions/rules/unnecessary_subscript_reversal.rs @@ -0,0 +1,77 @@ +use super::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::define_violation; +use crate::registry::Diagnostic; + +use crate::violation::Violation; +use num_bigint::BigInt; +use ruff_macros::derive_message_formats; +use rustpython_ast::{Constant, Expr, ExprKind, Unaryop}; + +define_violation!( + pub struct UnnecessarySubscriptReversal { + pub func: String, + } +); +impl Violation for UnnecessarySubscriptReversal { + #[derive_message_formats] + fn message(&self) -> String { + let UnnecessarySubscriptReversal { func } = self; + format!("Unnecessary subscript reversal of iterable within `{func}()`") + } +} + +/// C415 +pub fn unnecessary_subscript_reversal( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + args: &[Expr], +) { + let Some(first_arg) = args.first() else { + return; + }; + let Some(id) = helpers::function_name(func) else { + return; + }; + if !(id == "set" || id == "sorted" || id == "reversed") { + return; + } + if !checker.is_builtin(id) { + return; + } + let ExprKind::Subscript { slice, .. } = &first_arg.node else { + return; + }; + let ExprKind::Slice { lower, upper, step } = &slice.node else { + return; + }; + if lower.is_some() || upper.is_some() { + return; + } + let Some(step) = step.as_ref() else { + return; + }; + let ExprKind::UnaryOp { + op: Unaryop::USub, + operand, + } = &step.node else { + return; + }; + let ExprKind::Constant { + value: Constant::Int(val), + .. + } = &operand.node else { + return; + }; + if *val != BigInt::from(1) { + return; + }; + checker.diagnostics.push(Diagnostic::new( + UnnecessarySubscriptReversal { + func: id.to_string(), + }, + Range::from_located(expr), + )); +} diff --git a/src/violations.rs b/src/violations.rs index 852097dd24..e65c01351f 100644 --- a/src/violations.rs +++ b/src/violations.rs @@ -1396,290 +1396,6 @@ impl Violation for BlindExcept { } } -// flake8-comprehensions - -define_violation!( - pub struct UnnecessaryGeneratorList; -); -impl AlwaysAutofixableViolation for UnnecessaryGeneratorList { - #[derive_message_formats] - fn message(&self) -> String { - format!("Unnecessary generator (rewrite as a `list` comprehension)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `list` comprehension".to_string() - } -} - -define_violation!( - pub struct UnnecessaryGeneratorSet; -); -impl AlwaysAutofixableViolation for UnnecessaryGeneratorSet { - #[derive_message_formats] - fn message(&self) -> String { - format!("Unnecessary generator (rewrite as a `set` comprehension)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `set` comprehension".to_string() - } -} - -define_violation!( - pub struct UnnecessaryGeneratorDict; -); -impl AlwaysAutofixableViolation for UnnecessaryGeneratorDict { - #[derive_message_formats] - fn message(&self) -> String { - format!("Unnecessary generator (rewrite as a `dict` comprehension)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `dict` comprehension".to_string() - } -} - -define_violation!( - pub struct UnnecessaryListComprehensionSet; -); -impl AlwaysAutofixableViolation for UnnecessaryListComprehensionSet { - #[derive_message_formats] - fn message(&self) -> String { - format!("Unnecessary `list` comprehension (rewrite as a `set` comprehension)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `set` comprehension".to_string() - } -} - -define_violation!( - pub struct UnnecessaryListComprehensionDict; -); -impl AlwaysAutofixableViolation for UnnecessaryListComprehensionDict { - #[derive_message_formats] - fn message(&self) -> String { - format!("Unnecessary `list` comprehension (rewrite as a `dict` comprehension)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `dict` comprehension".to_string() - } -} - -define_violation!( - pub struct UnnecessaryLiteralSet { - pub obj_type: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryLiteralSet { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryLiteralSet { obj_type } = self; - format!("Unnecessary `{obj_type}` literal (rewrite as a `set` literal)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `set` literal".to_string() - } -} - -define_violation!( - pub struct UnnecessaryLiteralDict { - pub obj_type: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryLiteralDict { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryLiteralDict { obj_type } = self; - format!("Unnecessary `{obj_type}` literal (rewrite as a `dict` literal)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a `dict` literal".to_string() - } -} - -define_violation!( - pub struct UnnecessaryCollectionCall { - pub obj_type: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryCollectionCall { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryCollectionCall { obj_type } = self; - format!("Unnecessary `{obj_type}` call (rewrite as a literal)") - } - - fn autofix_title(&self) -> String { - "Rewrite as a literal".to_string() - } -} - -define_violation!( - pub struct UnnecessaryLiteralWithinTupleCall { - pub literal: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryLiteralWithinTupleCall { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryLiteralWithinTupleCall { literal } = self; - if literal == "list" { - format!( - "Unnecessary `{literal}` literal passed to `tuple()` (rewrite as a `tuple` \ - literal)" - ) - } else { - format!( - "Unnecessary `{literal}` literal passed to `tuple()` (remove the outer call to \ - `tuple()`)" - ) - } - } - - fn autofix_title(&self) -> String { - let UnnecessaryLiteralWithinTupleCall { literal } = self; - { - if literal == "list" { - "Rewrite as a `tuple` literal".to_string() - } else { - "Remove outer `tuple` call".to_string() - } - } - } -} - -define_violation!( - pub struct UnnecessaryLiteralWithinListCall { - pub literal: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryLiteralWithinListCall { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryLiteralWithinListCall { literal } = self; - if literal == "list" { - format!( - "Unnecessary `{literal}` literal passed to `list()` (remove the outer call to \ - `list()`)" - ) - } else { - format!( - "Unnecessary `{literal}` literal passed to `list()` (rewrite as a `list` literal)" - ) - } - } - - fn autofix_title(&self) -> String { - let UnnecessaryLiteralWithinListCall { literal } = self; - { - if literal == "list" { - "Remove outer `list` call".to_string() - } else { - "Rewrite as a `list` literal".to_string() - } - } - } -} - -define_violation!( - pub struct UnnecessaryListCall; -); -impl AlwaysAutofixableViolation for UnnecessaryListCall { - #[derive_message_formats] - fn message(&self) -> String { - format!("Unnecessary `list` call (remove the outer call to `list()`)") - } - - fn autofix_title(&self) -> String { - "Remove outer `list` call".to_string() - } -} - -define_violation!( - pub struct UnnecessaryCallAroundSorted { - pub func: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryCallAroundSorted { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryCallAroundSorted { func } = self; - format!("Unnecessary `{func}` call around `sorted()`") - } - - fn autofix_title(&self) -> String { - let UnnecessaryCallAroundSorted { func } = self; - format!("Remove unnecessary `{func}` call") - } -} - -define_violation!( - pub struct UnnecessaryDoubleCastOrProcess { - pub inner: String, - pub outer: String, - } -); -impl Violation for UnnecessaryDoubleCastOrProcess { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryDoubleCastOrProcess { inner, outer } = self; - format!("Unnecessary `{inner}` call within `{outer}()`") - } -} - -define_violation!( - pub struct UnnecessarySubscriptReversal { - pub func: String, - } -); -impl Violation for UnnecessarySubscriptReversal { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessarySubscriptReversal { func } = self; - format!("Unnecessary subscript reversal of iterable within `{func}()`") - } -} - -define_violation!( - pub struct UnnecessaryComprehension { - pub obj_type: String, - } -); -impl AlwaysAutofixableViolation for UnnecessaryComprehension { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryComprehension { obj_type } = self; - format!("Unnecessary `{obj_type}` comprehension (rewrite using `{obj_type}()`)") - } - - fn autofix_title(&self) -> String { - let UnnecessaryComprehension { obj_type } = self; - format!("Rewrite using `{obj_type}()`") - } -} - -define_violation!( - pub struct UnnecessaryMap { - pub obj_type: String, - } -); -impl Violation for UnnecessaryMap { - #[derive_message_formats] - fn message(&self) -> String { - let UnnecessaryMap { obj_type } = self; - if obj_type == "generator" { - format!("Unnecessary `map` usage (rewrite using a generator expression)") - } else { - format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)") - } - } -} - // flake8-debugger define_violation!(