diff --git a/crates/ruff/src/checkers/ast/analyze/expression.rs b/crates/ruff/src/checkers/ast/analyze/expression.rs index fe258f8362..42f552ced0 100644 --- a/crates/ruff/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff/src/checkers/ast/analyze/expression.rs @@ -429,7 +429,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { pyupgrade::rules::unnecessary_encode_utf8(checker, call); } if checker.enabled(Rule::RedundantOpenModes) { - pyupgrade::rules::redundant_open_modes(checker, expr); + pyupgrade::rules::redundant_open_modes(checker, call); } if checker.enabled(Rule::NativeLiterals) { pyupgrade::rules::native_literals(checker, expr, func, args, keywords); @@ -438,7 +438,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { pyupgrade::rules::open_alias(checker, expr, func); } if checker.enabled(Rule::ReplaceUniversalNewlines) { - pyupgrade::rules::replace_universal_newlines(checker, func, keywords); + pyupgrade::rules::replace_universal_newlines(checker, call); } if checker.enabled(Rule::ReplaceStdoutStderr) { pyupgrade::rules::replace_stdout_stderr(checker, call); @@ -461,7 +461,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { flake8_async::rules::blocking_os_call(checker, expr); } if checker.any_enabled(&[Rule::Print, Rule::PPrint]) { - flake8_print::rules::print_call(checker, func, keywords); + flake8_print::rules::print_call(checker, call); } if checker.any_enabled(&[ Rule::SuspiciousPickleUsage, @@ -513,13 +513,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { } if checker.enabled(Rule::ZipWithoutExplicitStrict) { if checker.settings.target_version >= PythonVersion::Py310 { - flake8_bugbear::rules::zip_without_explicit_strict( - checker, expr, func, args, keywords, - ); + flake8_bugbear::rules::zip_without_explicit_strict(checker, call); } } if checker.enabled(Rule::NoExplicitStacklevel) { - flake8_bugbear::rules::no_explicit_stacklevel(checker, func, keywords); + flake8_bugbear::rules::no_explicit_stacklevel(checker, call); } if checker.enabled(Rule::UnnecessaryDictKwargs) { flake8_pie::rules::unnecessary_dict_kwargs(checker, expr, keywords); @@ -531,19 +529,19 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { flake8_bandit::rules::bad_file_permissions(checker, call); } if checker.enabled(Rule::RequestWithNoCertValidation) { - flake8_bandit::rules::request_with_no_cert_validation(checker, func, keywords); + flake8_bandit::rules::request_with_no_cert_validation(checker, call); } if checker.enabled(Rule::UnsafeYAMLLoad) { flake8_bandit::rules::unsafe_yaml_load(checker, call); } if checker.enabled(Rule::SnmpInsecureVersion) { - flake8_bandit::rules::snmp_insecure_version(checker, func, keywords); + flake8_bandit::rules::snmp_insecure_version(checker, call); } if checker.enabled(Rule::SnmpWeakCryptography) { flake8_bandit::rules::snmp_weak_cryptography(checker, call); } if checker.enabled(Rule::Jinja2AutoescapeFalse) { - flake8_bandit::rules::jinja2_autoescape_false(checker, func, keywords); + flake8_bandit::rules::jinja2_autoescape_false(checker, call); } if checker.enabled(Rule::HardcodedPasswordFuncArg) { flake8_bandit::rules::hardcoded_password_func_arg(checker, keywords); @@ -555,13 +553,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { flake8_bandit::rules::hashlib_insecure_hash_functions(checker, call); } if checker.enabled(Rule::RequestWithoutTimeout) { - flake8_bandit::rules::request_without_timeout(checker, func, keywords); + flake8_bandit::rules::request_without_timeout(checker, call); } if checker.enabled(Rule::ParamikoCall) { flake8_bandit::rules::paramiko_call(checker, func); } if checker.enabled(Rule::LoggingConfigInsecureListen) { - flake8_bandit::rules::logging_config_insecure_listen(checker, func, keywords); + flake8_bandit::rules::logging_config_insecure_listen(checker, call); } if checker.any_enabled(&[ Rule::SubprocessWithoutShellEqualsTrue, @@ -572,7 +570,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { Rule::StartProcessWithPartialPath, Rule::UnixCommandWildcardInjection, ]) { - flake8_bandit::rules::shell_injection(checker, func, args, keywords); + flake8_bandit::rules::shell_injection(checker, call); } if checker.enabled(Rule::UnnecessaryGeneratorList) { flake8_comprehensions::rules::unnecessary_generator_list( @@ -679,19 +677,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { } pandas_vet::rules::call(checker, func); if checker.enabled(Rule::PandasUseOfDotReadTable) { - pandas_vet::rules::use_of_read_table(checker, func, keywords); + pandas_vet::rules::use_of_read_table(checker, call); } if checker.enabled(Rule::PandasUseOfPdMerge) { pandas_vet::rules::use_of_pd_merge(checker, func); } if checker.enabled(Rule::CallDatetimeWithoutTzinfo) { - flake8_datetimez::rules::call_datetime_without_tzinfo( - checker, - func, - args, - keywords, - expr.range(), - ); + flake8_datetimez::rules::call_datetime_without_tzinfo(checker, call); } if checker.enabled(Rule::CallDatetimeToday) { flake8_datetimez::rules::call_datetime_today(checker, func, expr.range()); @@ -707,30 +699,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { ); } if checker.enabled(Rule::CallDatetimeNowWithoutTzinfo) { - flake8_datetimez::rules::call_datetime_now_without_tzinfo( - checker, - func, - args, - keywords, - expr.range(), - ); + flake8_datetimez::rules::call_datetime_now_without_tzinfo(checker, call); } if checker.enabled(Rule::CallDatetimeFromtimestamp) { - flake8_datetimez::rules::call_datetime_fromtimestamp( - checker, - func, - args, - keywords, - expr.range(), - ); + flake8_datetimez::rules::call_datetime_fromtimestamp(checker, call); } if checker.enabled(Rule::CallDatetimeStrptimeWithoutZone) { - flake8_datetimez::rules::call_datetime_strptime_without_zone( - checker, - func, - args, - expr.range(), - ); + flake8_datetimez::rules::call_datetime_strptime_without_zone(checker, call); } if checker.enabled(Rule::CallDateToday) { flake8_datetimez::rules::call_date_today(checker, func, expr.range()); @@ -754,10 +729,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { pylint::rules::bad_str_strip_call(checker, func, args); } if checker.enabled(Rule::InvalidEnvvarDefault) { - pylint::rules::invalid_envvar_default(checker, func, args, keywords); + pylint::rules::invalid_envvar_default(checker, call); } if checker.enabled(Rule::InvalidEnvvarValue) { - pylint::rules::invalid_envvar_value(checker, func, args, keywords); + pylint::rules::invalid_envvar_value(checker, call); } if checker.enabled(Rule::NestedMinMax) { pylint::rules::nested_min_max(checker, expr, func, args, keywords); @@ -775,13 +750,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { } } if checker.enabled(Rule::SubprocessPopenPreexecFn) { - pylint::rules::subprocess_popen_preexec_fn(checker, func, keywords); + pylint::rules::subprocess_popen_preexec_fn(checker, call); } if checker.any_enabled(&[ Rule::PytestRaisesWithoutException, Rule::PytestRaisesTooBroad, ]) { - flake8_pytest_style::rules::raises_call(checker, func, args, keywords); + flake8_pytest_style::rules::raises_call(checker, call); } if checker.enabled(Rule::PytestFailWithoutMessage) { flake8_pytest_style::rules::fail_call(checker, call); @@ -855,7 +830,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { flake8_use_pathlib::rules::path_constructor_current_directory(checker, expr, func); } if checker.enabled(Rule::OsSepSplit) { - flake8_use_pathlib::rules::os_sep_split(checker, func, args, keywords); + flake8_use_pathlib::rules::os_sep_split(checker, call); } if checker.enabled(Rule::NumpyLegacyRandom) { numpy::rules::legacy_random(checker, func); @@ -876,7 +851,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { pylint::rules::logging_call(checker, call); } if checker.enabled(Rule::DjangoLocalsInRenderFunction) { - flake8_django::rules::locals_in_render_function(checker, func, args, keywords); + flake8_django::rules::locals_in_render_function(checker, call); } if checker.is_stub && checker.enabled(Rule::UnsupportedMethodCallOnAll) { flake8_pyi::rules::unsupported_method_call_on_all(checker, func); diff --git a/crates/ruff/src/rules/airflow/rules/task_variable_name.rs b/crates/ruff/src/rules/airflow/rules/task_variable_name.rs index 4bca6f3e25..9c73477af5 100644 --- a/crates/ruff/src/rules/airflow/rules/task_variable_name.rs +++ b/crates/ruff/src/rules/airflow/rules/task_variable_name.rs @@ -1,10 +1,8 @@ -use ruff_python_ast as ast; -use ruff_python_ast::{Arguments, Expr, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast as ast; use ruff_python_ast::Constant; +use ruff_python_ast::{Expr, Ranged}; use crate::checkers::ast::Checker; @@ -61,9 +59,7 @@ pub(crate) fn variable_name_task_id( // If the value is not a call, we can't do anything. let Expr::Call(ast::ExprCall { - func, - arguments: Arguments { keywords, .. }, - .. + func, arguments, .. }) = value else { return None; @@ -79,7 +75,7 @@ pub(crate) fn variable_name_task_id( } // If the call doesn't have a `task_id` keyword argument, we can't do anything. - let keyword = find_keyword(keywords, "task_id")?; + let keyword = arguments.find_keyword("task_id")?; // If the keyword argument is not a string, we can't do anything. let task_id = match &keyword.value { diff --git a/crates/ruff/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs b/crates/ruff/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs index 944cee6bbb..799a1268df 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/jinja2_autoescape_false.rs @@ -1,8 +1,6 @@ -use ruff_python_ast::helpers::find_keyword; -use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Constant, Expr, Ranged}; use crate::checkers::ast::Checker; @@ -59,13 +57,13 @@ impl Violation for Jinja2AutoescapeFalse { } /// S701 -pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { +pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["jinja2", "Environment"])) { - if let Some(keyword) = find_keyword(keywords, "autoescape") { + if let Some(keyword) = call.arguments.find_keyword("autoescape") { match &keyword.value { Expr::Constant(ast::ExprConstant { value: Constant::Bool(true), @@ -89,7 +87,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, func: &Expr, keywor } else { checker.diagnostics.push(Diagnostic::new( Jinja2AutoescapeFalse { value: false }, - func.range(), + call.func.range(), )); } } diff --git a/crates/ruff/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs b/crates/ruff/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs index aa9b1c23e7..5c936a87d6 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs @@ -1,8 +1,6 @@ -use ruff_python_ast::{Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; @@ -35,22 +33,19 @@ impl Violation for LoggingConfigInsecureListen { } /// S612 -pub(crate) fn logging_config_insecure_listen( - checker: &mut Checker, - func: &Expr, - keywords: &[Keyword], -) { +pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "config", "listen"])) { - if find_keyword(keywords, "verify").is_some() { + if call.arguments.find_keyword("verify").is_some() { return; } - checker - .diagnostics - .push(Diagnostic::new(LoggingConfigInsecureListen, func.range())); + checker.diagnostics.push(Diagnostic::new( + LoggingConfigInsecureListen, + call.func.range(), + )); } } diff --git a/crates/ruff/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs b/crates/ruff/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs index d2f7d511ce..843d317335 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/request_with_no_cert_validation.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::{Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_false}; +use ruff_python_ast::helpers::is_const_false; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; @@ -46,14 +45,10 @@ impl Violation for RequestWithNoCertValidation { } /// S501 -pub(crate) fn request_with_no_cert_validation( - checker: &mut Checker, - func: &Expr, - keywords: &[Keyword], -) { +pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast::ExprCall) { if let Some(target) = checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .and_then(|call_path| match call_path.as_slice() { ["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => { Some("requests") @@ -63,7 +58,7 @@ pub(crate) fn request_with_no_cert_validation( _ => None, }) { - if let Some(keyword) = find_keyword(keywords, "verify") { + if let Some(keyword) = call.arguments.find_keyword("verify") { if is_const_false(&keyword.value) { checker.diagnostics.push(Diagnostic::new( RequestWithNoCertValidation { diff --git a/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs b/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs index 746fcce202..37bf931fab 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/request_without_timeout.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::{Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_none}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; @@ -49,10 +48,10 @@ impl Violation for RequestWithoutTimeout { } /// S113 -pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { +pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!( call_path.as_slice(), @@ -63,7 +62,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywor ) }) { - if let Some(keyword) = find_keyword(keywords, "timeout") { + if let Some(keyword) = call.arguments.find_keyword("timeout") { if is_const_none(&keyword.value) { checker.diagnostics.push(Diagnostic::new( RequestWithoutTimeout { implicit: false }, @@ -73,7 +72,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywor } else { checker.diagnostics.push(Diagnostic::new( RequestWithoutTimeout { implicit: true }, - func.range(), + call.func.range(), )); } } diff --git a/crates/ruff/src/rules/flake8_bandit/rules/shell_injection.rs b/crates/ruff/src/rules/flake8_bandit/rules/shell_injection.rs index f0547ea91b..2b5715d18d 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/shell_injection.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/shell_injection.rs @@ -1,10 +1,9 @@ //! Checks relating to shell injection. -use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, Truthiness}; +use ruff_python_ast::helpers::Truthiness; +use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged}; use ruff_python_semantic::SemanticModel; use crate::{ @@ -144,17 +143,12 @@ impl Violation for UnixCommandWildcardInjection { } /// S602, S603, S604, S605, S606, S607, S609 -pub(crate) fn shell_injection( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let call_kind = get_call_kind(func, checker.semantic()); - let shell_keyword = find_shell_keyword(keywords, checker.semantic()); +pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) { + let call_kind = get_call_kind(&call.func, checker.semantic()); + let shell_keyword = find_shell_keyword(&call.arguments, checker.semantic()); if matches!(call_kind, Some(CallKind::Subprocess)) { - if let Some(arg) = args.first() { + if let Some(arg) = call.arguments.args.first() { match shell_keyword { // S602 Some(ShellKeyword { @@ -209,7 +203,7 @@ pub(crate) fn shell_injection( // S605 if checker.enabled(Rule::StartProcessWithAShell) { if matches!(call_kind, Some(CallKind::Shell)) { - if let Some(arg) = args.first() { + if let Some(arg) = call.arguments.args.first() { checker.diagnostics.push(Diagnostic::new( StartProcessWithAShell { seems_safe: shell_call_seems_safe(arg), @@ -225,14 +219,14 @@ pub(crate) fn shell_injection( if matches!(call_kind, Some(CallKind::NoShell)) { checker .diagnostics - .push(Diagnostic::new(StartProcessWithNoShell, func.range())); + .push(Diagnostic::new(StartProcessWithNoShell, call.func.range())); } } // S607 if checker.enabled(Rule::StartProcessWithPartialPath) { if call_kind.is_some() { - if let Some(arg) = args.first() { + if let Some(arg) = call.arguments.args.first() { if is_partial_path(arg) { checker .diagnostics @@ -256,11 +250,12 @@ pub(crate) fn shell_injection( ) ) { - if let Some(arg) = args.first() { + if let Some(arg) = call.arguments.args.first() { if is_wildcard_command(arg) { - checker - .diagnostics - .push(Diagnostic::new(UnixCommandWildcardInjection, func.range())); + checker.diagnostics.push(Diagnostic::new( + UnixCommandWildcardInjection, + call.func.range(), + )); } } } @@ -317,10 +312,10 @@ struct ShellKeyword<'a> { /// Return the `shell` keyword argument to the given function call, if any. fn find_shell_keyword<'a>( - keywords: &'a [Keyword], + arguments: &'a Arguments, semantic: &SemanticModel, ) -> Option> { - find_keyword(keywords, "shell").map(|keyword| ShellKeyword { + arguments.find_keyword("shell").map(|keyword| ShellKeyword { truthiness: Truthiness::from_expr(&keyword.value, |id| semantic.is_builtin(id)), keyword, }) diff --git a/crates/ruff/src/rules/flake8_bandit/rules/snmp_insecure_version.rs b/crates/ruff/src/rules/flake8_bandit/rules/snmp_insecure_version.rs index a05c647249..845dc3affc 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/snmp_insecure_version.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/snmp_insecure_version.rs @@ -1,9 +1,8 @@ use num_traits::{One, Zero}; -use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Constant, Expr, Ranged}; use crate::checkers::ast::Checker; @@ -43,15 +42,15 @@ impl Violation for SnmpInsecureVersion { } /// S508 -pub(crate) fn snmp_insecure_version(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { +pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!(call_path.as_slice(), ["pysnmp", "hlapi", "CommunityData"]) }) { - if let Some(keyword) = find_keyword(keywords, "mpModel") { + if let Some(keyword) = call.arguments.find_keyword("mpModel") { if let Expr::Constant(ast::ExprConstant { value: Constant::Int(value), .. diff --git a/crates/ruff/src/rules/flake8_blind_except/rules/blind_except.rs b/crates/ruff/src/rules/flake8_blind_except/rules/blind_except.rs index ec28044c25..4fa9474d01 100644 --- a/crates/ruff/src/rules/flake8_blind_except/rules/blind_except.rs +++ b/crates/ruff/src/rules/flake8_blind_except/rules/blind_except.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, Stmt}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_true}; +use ruff_python_ast::helpers::is_const_true; +use ruff_python_ast::{self as ast, Expr, Ranged, Stmt}; use ruff_python_semantic::analyze::logging; use crate::checkers::ast::Checker; @@ -95,9 +94,7 @@ pub(crate) fn blind_except( if body.iter().any(|stmt| { if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt { if let Expr::Call(ast::ExprCall { - func, - arguments: Arguments { keywords, .. }, - .. + func, arguments, .. }) = value.as_ref() { if logging::is_logger_candidate( @@ -111,7 +108,7 @@ pub(crate) fn blind_except( return true; } if attr == "error" { - if let Some(keyword) = find_keyword(keywords, "exc_info") { + if let Some(keyword) = arguments.find_keyword("exc_info") { if is_const_true(&keyword.value) { return true; } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/assert_raises_exception.rs b/crates/ruff/src/rules/flake8_bugbear/rules/assert_raises_exception.rs index 114b450155..908fd6e7de 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/assert_raises_exception.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/assert_raises_exception.rs @@ -1,10 +1,8 @@ use std::fmt; -use ruff_python_ast::{self as ast, Arguments, Expr, Ranged, WithItem}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Expr, Ranged, WithItem}; use crate::checkers::ast::Checker; @@ -81,27 +79,24 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem]) for item in items { let Expr::Call(ast::ExprCall { func, - arguments: - Arguments { - args, - keywords, - range: _, - }, + arguments, range: _, }) = &item.context_expr else { return; }; - if args.len() != 1 { - return; - } + if item.optional_vars.is_some() { return; } + let [arg] = arguments.args.as_slice() else { + return; + }; + let Some(exception) = checker .semantic() - .resolve_call_path(args.first().unwrap()) + .resolve_call_path(arg) .and_then(|call_path| match call_path.as_slice() { ["", "Exception"] => Some(ExceptionKind::Exception), ["", "BaseException"] => Some(ExceptionKind::BaseException), @@ -118,7 +113,7 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem]) .semantic() .resolve_call_path(func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "raises"])) - && find_keyword(keywords, "match").is_none() + && arguments.find_keyword("match").is_none() { AssertionKind::PytestRaises } else { diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs index 2afaff0d78..c964e54d68 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -1,8 +1,6 @@ -use ruff_python_ast::{Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; @@ -38,20 +36,20 @@ impl Violation for NoExplicitStacklevel { } /// B028 -pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { +pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["warnings", "warn"])) { return; } - if find_keyword(keywords, "stacklevel").is_some() { + if call.arguments.find_keyword("stacklevel").is_some() { return; } checker .diagnostics - .push(Diagnostic::new(NoExplicitStacklevel, func.range())); + .push(Diagnostic::new(NoExplicitStacklevel, call.func.range())); } diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index 03c7f96a73..c1ca58886b 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_none}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Arguments, Expr, Ranged}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; @@ -41,24 +40,20 @@ impl Violation for ZipWithoutExplicitStrict { } /// B905 -pub(crate) fn zip_without_explicit_strict( - checker: &mut Checker, - expr: &Expr, - func: &Expr, - args: &[Expr], - kwargs: &[Keyword], -) { - if let Expr::Name(ast::ExprName { id, .. }) = func { +pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::ExprCall) { + if let Expr::Name(ast::ExprName { id, .. }) = call.func.as_ref() { if id == "zip" && checker.semantic().is_builtin("zip") - && find_keyword(kwargs, "strict").is_none() - && !args + && call.arguments.find_keyword("strict").is_none() + && !call + .arguments + .args .iter() .any(|arg| is_infinite_iterator(arg, checker.semantic())) { checker .diagnostics - .push(Diagnostic::new(ZipWithoutExplicitStrict, expr.range())); + .push(Diagnostic::new(ZipWithoutExplicitStrict, call.range())); } } } diff --git a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs index 3fbc5ed40e..f0c86f3271 100644 --- a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs +++ b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs @@ -1,11 +1,10 @@ -use ruff_python_ast::{Expr, Keyword}; -use ruff_text_size::TextRange; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{has_non_none_keyword, is_const_none}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; +use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; use super::helpers; @@ -22,16 +21,10 @@ impl Violation for CallDatetimeFromtimestamp { } /// DTZ006 -pub(crate) fn call_datetime_fromtimestamp( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], - location: TextRange, -) { +pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!( call_path.as_slice(), @@ -47,25 +40,25 @@ pub(crate) fn call_datetime_fromtimestamp( } // no args / no args unqualified - if args.len() < 2 && keywords.is_empty() { + if call.arguments.args.len() < 2 && call.arguments.keywords.is_empty() { checker .diagnostics - .push(Diagnostic::new(CallDatetimeFromtimestamp, location)); + .push(Diagnostic::new(CallDatetimeFromtimestamp, call.range())); return; } // none args - if args.len() > 1 && is_const_none(&args[1]) { + if call.arguments.args.len() > 1 && is_const_none(&call.arguments.args[1]) { checker .diagnostics - .push(Diagnostic::new(CallDatetimeFromtimestamp, location)); + .push(Diagnostic::new(CallDatetimeFromtimestamp, call.range())); return; } // wrong keywords / none keyword - if !keywords.is_empty() && !has_non_none_keyword(keywords, "tz") { + if !call.arguments.keywords.is_empty() && !has_non_none_keyword(&call.arguments, "tz") { checker .diagnostics - .push(Diagnostic::new(CallDatetimeFromtimestamp, location)); + .push(Diagnostic::new(CallDatetimeFromtimestamp, call.range())); } } diff --git a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs index 55b907d791..9ebc9ecb27 100644 --- a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs +++ b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs @@ -1,11 +1,10 @@ -use ruff_python_ast::{Expr, Keyword}; -use ruff_text_size::TextRange; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{has_non_none_keyword, is_const_none}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; +use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; use super::helpers; @@ -20,16 +19,10 @@ impl Violation for CallDatetimeNowWithoutTzinfo { } /// DTZ005 -pub(crate) fn call_datetime_now_without_tzinfo( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], - location: TextRange, -) { +pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "datetime", "now"])) { return; @@ -40,25 +33,25 @@ pub(crate) fn call_datetime_now_without_tzinfo( } // no args / no args unqualified - if args.is_empty() && keywords.is_empty() { + if call.arguments.args.is_empty() && call.arguments.keywords.is_empty() { checker .diagnostics - .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, location)); + .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range())); return; } // none args - if !args.is_empty() && is_const_none(&args[0]) { + if call.arguments.args.first().is_some_and(is_const_none) { checker .diagnostics - .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, location)); + .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range())); return; } // wrong keywords / none keyword - if !keywords.is_empty() && !has_non_none_keyword(keywords, "tz") { + if !call.arguments.keywords.is_empty() && !has_non_none_keyword(&call.arguments, "tz") { checker .diagnostics - .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, location)); + .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range())); } } diff --git a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs index 3af5ce95a4..0cde6b272e 100644 --- a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs +++ b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs @@ -1,11 +1,9 @@ -use ruff_python_ast::{self as ast, Arguments, Constant, Expr}; -use ruff_text_size::TextRange; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::has_non_none_keyword; +use ruff_python_ast::{self as ast, Constant, Expr, Ranged}; use crate::checkers::ast::Checker; +use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; #[violation] pub struct CallDatetimeStrptimeWithoutZone; @@ -21,15 +19,10 @@ impl Violation for CallDatetimeStrptimeWithoutZone { } /// DTZ007 -pub(crate) fn call_datetime_strptime_without_zone( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - location: TextRange, -) { +pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| { matches!(call_path.as_slice(), ["datetime", "datetime", "strptime"]) }) @@ -42,7 +35,7 @@ pub(crate) fn call_datetime_strptime_without_zone( value: Constant::Str(format), kind: None, range: _, - })) = args.get(1).as_ref() + })) = call.arguments.args.get(1).as_ref() { if format.contains("%z") { return; @@ -53,17 +46,14 @@ pub(crate) fn call_datetime_strptime_without_zone( checker.semantic().expr_grandparent(), checker.semantic().expr_parent(), ) else { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeStrptimeWithoutZone, location)); + checker.diagnostics.push(Diagnostic::new( + CallDatetimeStrptimeWithoutZone, + call.range(), + )); return; }; - if let Expr::Call(ast::ExprCall { - arguments: Arguments { keywords, .. }, - .. - }) = grandparent - { + if let Expr::Call(ast::ExprCall { arguments, .. }) = grandparent { if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = parent { let attr = attr.as_str(); // Ex) `datetime.strptime(...).astimezone()` @@ -73,14 +63,15 @@ pub(crate) fn call_datetime_strptime_without_zone( // Ex) `datetime.strptime(...).replace(tzinfo=UTC)` if attr == "replace" { - if has_non_none_keyword(keywords, "tzinfo") { + if has_non_none_keyword(arguments, "tzinfo") { return; } } } } - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeStrptimeWithoutZone, location)); + checker.diagnostics.push(Diagnostic::new( + CallDatetimeStrptimeWithoutZone, + call.range(), + )); } diff --git a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs index dfce5e2c38..0c0f6b36f4 100644 --- a/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs +++ b/crates/ruff/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs @@ -1,11 +1,10 @@ -use ruff_python_ast::{Expr, Keyword}; -use ruff_text_size::TextRange; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{has_non_none_keyword, is_const_none}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; +use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; use super::helpers; @@ -44,16 +43,10 @@ impl Violation for CallDatetimeWithoutTzinfo { } } -pub(crate) fn call_datetime_without_tzinfo( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], - location: TextRange, -) { +pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["datetime", "datetime"])) { return; @@ -64,17 +57,17 @@ pub(crate) fn call_datetime_without_tzinfo( } // No positional arg: keyword is missing or constant None. - if args.len() < 8 && !has_non_none_keyword(keywords, "tzinfo") { + if call.arguments.args.len() < 8 && !has_non_none_keyword(&call.arguments, "tzinfo") { checker .diagnostics - .push(Diagnostic::new(CallDatetimeWithoutTzinfo, location)); + .push(Diagnostic::new(CallDatetimeWithoutTzinfo, call.range())); return; } // Positional arg: is constant None. - if args.len() >= 8 && is_const_none(&args[7]) { + if call.arguments.args.get(7).is_some_and(is_const_none) { checker .diagnostics - .push(Diagnostic::new(CallDatetimeWithoutTzinfo, location)); + .push(Diagnostic::new(CallDatetimeWithoutTzinfo, call.range())); } } diff --git a/crates/ruff/src/rules/flake8_datetimez/rules/helpers.rs b/crates/ruff/src/rules/flake8_datetimez/rules/helpers.rs index 9fda0737d8..48ccb8af23 100644 --- a/crates/ruff/src/rules/flake8_datetimez/rules/helpers.rs +++ b/crates/ruff/src/rules/flake8_datetimez/rules/helpers.rs @@ -1,11 +1,19 @@ -use ruff_python_ast::{Expr, ExprAttribute}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{Arguments, Expr, ExprAttribute}; use crate::checkers::ast::Checker; /// Check if the parent expression is a call to `astimezone`. This assumes that /// the current expression is a `datetime.datetime` object. -pub(crate) fn parent_expr_is_astimezone(checker: &Checker) -> bool { +pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool { checker.semantic().expr_parent().is_some_and( |parent| { matches!(parent, Expr::Attribute(ExprAttribute { attr, .. }) if attr.as_str() == "astimezone") }) } + +/// Return `true` if a keyword argument is present with a non-`None` value. +pub(super) fn has_non_none_keyword(arguments: &Arguments, keyword: &str) -> bool { + arguments + .find_keyword(keyword) + .is_some_and(|keyword| !is_const_none(&keyword.value)) +} diff --git a/crates/ruff/src/rules/flake8_django/rules/locals_in_render_function.rs b/crates/ruff/src/rules/flake8_django/rules/locals_in_render_function.rs index 5979ea1f0a..eb7c62682f 100644 --- a/crates/ruff/src/rules/flake8_django/rules/locals_in_render_function.rs +++ b/crates/ruff/src/rules/flake8_django/rules/locals_in_render_function.rs @@ -1,8 +1,6 @@ -use ruff_python_ast::{self as ast, Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Expr, Ranged}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; @@ -45,38 +43,23 @@ impl Violation for DjangoLocalsInRenderFunction { } /// DJ003 -pub(crate) fn locals_in_render_function( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { +pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprCall) { if !checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["django", "shortcuts", "render"])) { return; } - let locals = if args.len() >= 3 { - if !is_locals_call(&args[2], checker.semantic()) { - return; + if let Some(argument) = call.arguments.find_argument("context", 2) { + if is_locals_call(argument, checker.semantic()) { + checker.diagnostics.push(Diagnostic::new( + DjangoLocalsInRenderFunction, + argument.range(), + )); } - &args[2] - } else if let Some(keyword) = find_keyword(keywords, "context") { - if !is_locals_call(&keyword.value, checker.semantic()) { - return; - } - &keyword.value - } else { - return; - }; - - checker.diagnostics.push(Diagnostic::new( - DjangoLocalsInRenderFunction, - locals.range(), - )); + } } fn is_locals_call(expr: &Expr, semantic: &SemanticModel) -> bool { diff --git a/crates/ruff/src/rules/flake8_print/rules/print_call.rs b/crates/ruff/src/rules/flake8_print/rules/print_call.rs index f423c778d6..4b52aea891 100644 --- a/crates/ruff/src/rules/flake8_print/rules/print_call.rs +++ b/crates/ruff/src/rules/flake8_print/rules/print_call.rs @@ -1,8 +1,7 @@ -use ruff_python_ast::{Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_none}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -76,16 +75,16 @@ impl Violation for PPrint { } /// T201, T203 -pub(crate) fn print_call(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { +pub(crate) fn print_call(checker: &mut Checker, call: &ast::ExprCall) { let diagnostic = { - let call_path = checker.semantic().resolve_call_path(func); + let call_path = checker.semantic().resolve_call_path(&call.func); if call_path .as_ref() .is_some_and(|call_path| matches!(call_path.as_slice(), ["", "print"])) { // If the print call has a `file=` argument (that isn't `None`, `"sys.stdout"`, // or `"sys.stderr"`), don't trigger T201. - if let Some(keyword) = find_keyword(keywords, "file") { + if let Some(keyword) = call.arguments.find_keyword("file") { if !is_const_none(&keyword.value) { if checker.semantic().resolve_call_path(&keyword.value).map_or( true, @@ -98,12 +97,12 @@ pub(crate) fn print_call(checker: &mut Checker, func: &Expr, keywords: &[Keyword } } } - Diagnostic::new(Print, func.range()) + Diagnostic::new(Print, call.func.range()) } else if call_path .as_ref() .is_some_and(|call_path| matches!(call_path.as_slice(), ["pprint", "pprint"])) { - Diagnostic::new(PPrint, func.range()) + Diagnostic::new(PPrint, call.func.range()) } else { return; } diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs index 47572b674a..51c35099d5 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/fixture.rs @@ -4,7 +4,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Violation}; use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::collect_call_path; -use ruff_python_ast::helpers::{find_keyword, includes_arg_name}; +use ruff_python_ast::helpers::includes_arg_name; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; @@ -506,7 +506,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D } if checker.enabled(Rule::PytestExtraneousScopeFunction) { - if let Some(keyword) = find_keyword(&arguments.keywords, "scope") { + if let Some(keyword) = arguments.find_keyword("scope") { if keyword_is_literal(keyword, "function") { let mut diagnostic = Diagnostic::new(PytestExtraneousScopeFunction, keyword.range()); diff --git a/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs b/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs index 4d40fe8d8e..dae4157ab7 100644 --- a/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs +++ b/crates/ruff/src/rules/flake8_pytest_style/rules/raises.rs @@ -1,10 +1,9 @@ -use ruff_python_ast::helpers::{find_keyword, is_compound_statement}; -use ruff_python_ast::{self as ast, Expr, Keyword, Ranged, Stmt, WithItem}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::format_call_path; use ruff_python_ast::call_path::from_qualified_name; +use ruff_python_ast::helpers::is_compound_statement; +use ruff_python_ast::{self as ast, Expr, Ranged, Stmt, WithItem}; use ruff_python_semantic::SemanticModel; use crate::checkers::ast::Checker; @@ -91,19 +90,20 @@ const fn is_non_trivial_with_body(body: &[Stmt]) -> bool { } } -pub(crate) fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) { - if is_pytest_raises(func, checker.semantic()) { +pub(crate) fn raises_call(checker: &mut Checker, call: &ast::ExprCall) { + if is_pytest_raises(&call.func, checker.semantic()) { if checker.enabled(Rule::PytestRaisesWithoutException) { - if args.is_empty() && keywords.is_empty() { - checker - .diagnostics - .push(Diagnostic::new(PytestRaisesWithoutException, func.range())); + if call.arguments.is_empty() { + checker.diagnostics.push(Diagnostic::new( + PytestRaisesWithoutException, + call.func.range(), + )); } } if checker.enabled(Rule::PytestRaisesTooBroad) { - let match_keyword = find_keyword(keywords, "match"); - if let Some(exception) = args.first() { + let match_keyword = call.arguments.find_keyword("match"); + if let Some(exception) = call.arguments.args.first() { if let Some(match_keyword) = match_keyword { if is_empty_or_null_string(&match_keyword.value) { exception_needs_match(checker, exception); diff --git a/crates/ruff/src/rules/flake8_use_pathlib/rules/os_sep_split.rs b/crates/ruff/src/rules/flake8_use_pathlib/rules/os_sep_split.rs index fb43b537d5..0db62b142c 100644 --- a/crates/ruff/src/rules/flake8_use_pathlib/rules/os_sep_split.rs +++ b/crates/ruff/src/rules/flake8_use_pathlib/rules/os_sep_split.rs @@ -1,7 +1,6 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; -use ruff_python_ast::{Expr, ExprAttribute, Keyword, Ranged}; +use ruff_python_ast::{self as ast, Expr, ExprAttribute, Ranged}; use crate::checkers::ast::Checker; @@ -52,13 +51,8 @@ impl Violation for OsSepSplit { } /// PTH206 -pub(crate) fn os_sep_split( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { - let Expr::Attribute(ExprAttribute { attr, .. }) = func else { +pub(crate) fn os_sep_split(checker: &mut Checker, call: &ast::ExprCall) { + let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() else { return; }; @@ -66,19 +60,8 @@ pub(crate) fn os_sep_split( return; }; - let sep = if !args.is_empty() { - // `.split(os.sep)` - let [arg] = args else { - return; - }; - arg - } else if !keywords.is_empty() { - // `.split(sep=os.sep)` - let Some(keyword) = find_keyword(keywords, "sep") else { - return; - }; - &keyword.value - } else { + // Match `.split(os.sep)` or `.split(sep=os.sep)` + let Some(sep) = call.arguments.find_argument("sep", 0) else { return; }; diff --git a/crates/ruff/src/rules/pandas_vet/rules/read_table.rs b/crates/ruff/src/rules/pandas_vet/rules/read_table.rs index 5f8b671576..9f856ba82d 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/read_table.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/read_table.rs @@ -1,9 +1,7 @@ -use ruff_python_ast as ast; -use ruff_python_ast::{Constant, Expr, Keyword, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast as ast; +use ruff_python_ast::{Constant, Expr, Ranged}; use crate::checkers::ast::Checker; @@ -46,21 +44,24 @@ impl Violation for PandasUseOfDotReadTable { } /// PD012 -pub(crate) fn use_of_read_table(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) { +pub(crate) fn use_of_read_table(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["pandas", "read_table"])) { if let Some(Expr::Constant(ast::ExprConstant { value: Constant::Str(value), .. - })) = find_keyword(keywords, "sep").map(|keyword| &keyword.value) + })) = call + .arguments + .find_keyword("sep") + .map(|keyword| &keyword.value) { if value.as_str() == "," { checker .diagnostics - .push(Diagnostic::new(PandasUseOfDotReadTable, func.range())); + .push(Diagnostic::new(PandasUseOfDotReadTable, call.func.range())); } } } diff --git a/crates/ruff/src/rules/pylint/helpers.rs b/crates/ruff/src/rules/pylint/helpers.rs index 139485c872..425771c55b 100644 --- a/crates/ruff/src/rules/pylint/helpers.rs +++ b/crates/ruff/src/rules/pylint/helpers.rs @@ -1,20 +1,16 @@ use std::fmt; use ruff_python_ast as ast; -use ruff_python_ast::helpers::find_keyword; -use ruff_python_ast::{CmpOp, Constant, Expr, Keyword}; - +use ruff_python_ast::{Arguments, CmpOp, Constant, Expr}; use ruff_python_semantic::analyze::function_type; use ruff_python_semantic::{ScopeKind, SemanticModel}; use crate::settings::Settings; /// Returns the value of the `name` parameter to, e.g., a `TypeVar` constructor. -pub(super) fn type_param_name<'a>(args: &'a [Expr], keywords: &'a [Keyword]) -> Option<&'a str> { +pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> { // Handle both `TypeVar("T")` and `TypeVar(name="T")`. - let name_param = find_keyword(keywords, "name") - .map(|keyword| &keyword.value) - .or_else(|| args.get(0))?; + let name_param = arguments.find_argument("name", 0)?; if let Expr::Constant(ast::ExprConstant { value: Constant::Str(name), .. diff --git a/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs index fda341ddd8..bbe2e40581 100644 --- a/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs @@ -1,8 +1,6 @@ -use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Operator, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Constant, Expr, Operator, Ranged}; use crate::checkers::ast::Checker; @@ -78,22 +76,14 @@ fn is_valid_default(expr: &Expr) -> bool { } /// PLW1508 -pub(crate) fn invalid_envvar_default( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { +pub(crate) fn invalid_envvar_default(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "getenv"])) { // Find the `default` argument, if it exists. - let Some(expr) = args - .get(1) - .or_else(|| find_keyword(keywords, "default").map(|keyword| &keyword.value)) - else { + let Some(expr) = call.arguments.find_argument("default", 1) else { return; }; diff --git a/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs index eb2ad08e23..eccbd4077f 100644 --- a/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff/src/rules/pylint/rules/invalid_envvar_value.rs @@ -1,8 +1,6 @@ -use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Operator, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Constant, Expr, Operator, Ranged}; use crate::checkers::ast::Checker; @@ -75,22 +73,14 @@ fn is_valid_key(expr: &Expr) -> bool { } /// PLE1507 -pub(crate) fn invalid_envvar_value( - checker: &mut Checker, - func: &Expr, - args: &[Expr], - keywords: &[Keyword], -) { +pub(crate) fn invalid_envvar_value(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "getenv"])) { // Find the `key` argument, if it exists. - let Some(expr) = args - .get(0) - .or_else(|| find_keyword(keywords, "key").map(|keyword| &keyword.value)) - else { + let Some(expr) = call.arguments.find_argument("key", 0) else { return; }; diff --git a/crates/ruff/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs b/crates/ruff/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs index c4f090ed46..4094991b5a 100644 --- a/crates/ruff/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs +++ b/crates/ruff/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs @@ -1,7 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_none}; -use ruff_python_ast::{Expr, Keyword, Ranged}; +use ruff_python_ast::helpers::is_const_none; +use ruff_python_ast::{self as ast, Ranged}; use crate::checkers::ast::Checker; @@ -48,14 +48,16 @@ impl Violation for SubprocessPopenPreexecFn { } /// PLW1509 -pub(crate) fn subprocess_popen_preexec_fn(checker: &mut Checker, func: &Expr, kwargs: &[Keyword]) { +pub(crate) fn subprocess_popen_preexec_fn(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "Popen"])) { - if let Some(keyword) = - find_keyword(kwargs, "preexec_fn").filter(|keyword| !is_const_none(&keyword.value)) + if let Some(keyword) = call + .arguments + .find_keyword("preexec_fn") + .filter(|keyword| !is_const_none(&keyword.value)) { checker .diagnostics diff --git a/crates/ruff/src/rules/pylint/rules/type_bivariance.rs b/crates/ruff/src/rules/pylint/rules/type_bivariance.rs index 017a903b1e..fdeefdad79 100644 --- a/crates/ruff/src/rules/pylint/rules/type_bivariance.rs +++ b/crates/ruff/src/rules/pylint/rules/type_bivariance.rs @@ -1,10 +1,9 @@ use std::fmt; -use ruff_python_ast::{self as ast, Arguments, Expr, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_true}; +use ruff_python_ast::helpers::is_const_true; +use ruff_python_ast::{self as ast, Expr, Ranged}; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; @@ -75,19 +74,22 @@ impl Violation for TypeBivariance { /// PLC0131 pub(crate) fn type_bivariance(checker: &mut Checker, value: &Expr) { let Expr::Call(ast::ExprCall { - func, - arguments: Arguments { args, keywords, .. }, - .. + func, arguments, .. }) = value else { return; }; - let Some(covariant) = find_keyword(keywords, "covariant").map(|keyword| &keyword.value) else { + let Some(covariant) = arguments + .find_keyword("covariant") + .map(|keyword| &keyword.value) + else { return; }; - let Some(contravariant) = find_keyword(keywords, "contravariant").map(|keyword| &keyword.value) + let Some(contravariant) = arguments + .find_keyword("contravariant") + .map(|keyword| &keyword.value) else { return; }; @@ -118,7 +120,7 @@ pub(crate) fn type_bivariance(checker: &mut Checker, value: &Expr) { checker.diagnostics.push(Diagnostic::new( TypeBivariance { kind, - param_name: type_param_name(args, keywords).map(ToString::to_string), + param_name: type_param_name(arguments).map(ToString::to_string), }, func.range(), )); diff --git a/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs b/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs index b77612ed41..c4ddf89763 100644 --- a/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs +++ b/crates/ruff/src/rules/pylint/rules/type_name_incorrect_variance.rs @@ -1,10 +1,9 @@ use std::fmt; -use ruff_python_ast::{self as ast, Arguments, Expr, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_const_true}; +use ruff_python_ast::helpers::is_const_true; +use ruff_python_ast::{self as ast, Expr, Ranged}; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; @@ -66,21 +65,23 @@ impl Violation for TypeNameIncorrectVariance { /// PLC0105 pub(crate) fn type_name_incorrect_variance(checker: &mut Checker, value: &Expr) { let Expr::Call(ast::ExprCall { - func, - arguments: Arguments { args, keywords, .. }, - .. + func, arguments, .. }) = value else { return; }; - let Some(param_name) = type_param_name(args, keywords) else { + let Some(param_name) = type_param_name(arguments) else { return; }; - let covariant = find_keyword(keywords, "covariant").map(|keyword| &keyword.value); + let covariant = arguments + .find_keyword("covariant") + .map(|keyword| &keyword.value); - let contravariant = find_keyword(keywords, "contravariant").map(|keyword| &keyword.value); + let contravariant = arguments + .find_keyword("contravariant") + .map(|keyword| &keyword.value); if !mismatch(param_name, covariant, contravariant) { return; diff --git a/crates/ruff/src/rules/pylint/rules/type_param_name_mismatch.rs b/crates/ruff/src/rules/pylint/rules/type_param_name_mismatch.rs index e2eaf3194a..31db9780d7 100644 --- a/crates/ruff/src/rules/pylint/rules/type_param_name_mismatch.rs +++ b/crates/ruff/src/rules/pylint/rules/type_param_name_mismatch.rs @@ -1,9 +1,8 @@ use std::fmt; -use ruff_python_ast::{self as ast, Arguments, Expr, Ranged}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr, Ranged}; use crate::checkers::ast::Checker; use crate::rules::pylint::helpers::type_param_name; @@ -68,15 +67,13 @@ pub(crate) fn type_param_name_mismatch(checker: &mut Checker, value: &Expr, targ }; let Expr::Call(ast::ExprCall { - func, - arguments: Arguments { args, keywords, .. }, - .. + func, arguments, .. }) = value else { return; }; - let Some(param_name) = type_param_name(args, keywords) else { + let Some(param_name) = type_param_name(arguments) else { return; }; diff --git a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index 4a23cbf2a0..e59f0b50e5 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -1,16 +1,16 @@ use anyhow::{bail, Result}; use log::debug; -use ruff_python_ast::{ - self as ast, Arguments, Constant, Expr, ExprContext, Identifier, Keyword, Ranged, Stmt, -}; -use ruff_text_size::TextRange; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::{find_keyword, is_dunder}; +use ruff_python_ast::helpers::is_dunder; +use ruff_python_ast::{ + self as ast, Arguments, Constant, Expr, ExprContext, Identifier, Keyword, Ranged, Stmt, +}; use ruff_python_codegen::Generator; use ruff_python_semantic::SemanticModel; use ruff_python_stdlib::identifiers::is_identifier; +use ruff_text_size::TextRange; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -70,14 +70,14 @@ fn match_typed_dict_assign<'a>( targets: &'a [Expr], value: &'a Expr, semantic: &SemanticModel, -) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a Expr)> { +) -> Option<(&'a str, &'a Arguments, &'a Expr)> { let target = targets.get(0)?; let Expr::Name(ast::ExprName { id: class_name, .. }) = target else { return None; }; let Expr::Call(ast::ExprCall { func, - arguments: Arguments { args, keywords, .. }, + arguments, range: _, }) = value else { @@ -86,7 +86,7 @@ fn match_typed_dict_assign<'a>( if !semantic.match_typing_expr(func, "TypedDict") { return None; } - Some((class_name, args, keywords, func)) + Some((class_name, arguments, func)) } /// Generate a [`Stmt::AnnAssign`] representing the provided property @@ -201,17 +201,14 @@ fn properties_from_keywords(keywords: &[Keyword]) -> Result> { .collect() } -fn match_properties_and_total<'a>( - args: &'a [Expr], - keywords: &'a [Keyword], -) -> Result<(Vec, Option<&'a Keyword>)> { +fn match_properties_and_total(arguments: &Arguments) -> Result<(Vec, Option<&Keyword>)> { // We don't have to manage the hybrid case because it's not possible to have a // dict and keywords. For example, the following is illegal: // ``` // MyType = TypedDict('MyType', {'a': int, 'b': str}, a=int, b=str) // ``` - if let Some(dict) = args.get(1) { - let total = find_keyword(keywords, "total"); + if let Some(dict) = arguments.args.get(1) { + let total = arguments.find_keyword("total"); match dict { Expr::Dict(ast::ExprDict { keys, @@ -225,8 +222,8 @@ fn match_properties_and_total<'a>( }) => Ok((properties_from_dict_call(func, keywords)?, total)), _ => bail!("Expected `arg` to be `Expr::Dict` or `Expr::Call`"), } - } else if !keywords.is_empty() { - Ok((properties_from_keywords(keywords)?, None)) + } else if !arguments.keywords.is_empty() { + Ok((properties_from_keywords(&arguments.keywords)?, None)) } else { let node = Stmt::Pass(ast::StmtPass { range: TextRange::default(), @@ -262,13 +259,13 @@ pub(crate) fn convert_typed_dict_functional_to_class( targets: &[Expr], value: &Expr, ) { - let Some((class_name, args, keywords, base_class)) = + let Some((class_name, arguments, base_class)) = match_typed_dict_assign(targets, value, checker.semantic()) else { return; }; - let (body, total_keyword) = match match_properties_and_total(args, keywords) { + let (body, total_keyword) = match match_properties_and_total(arguments) { Ok((body, total_keyword)) => (body, total_keyword), Err(err) => { debug!("Skipping ineligible `TypedDict` \"{class_name}\": {err}"); diff --git a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs index 5b639f074c..1e45960d33 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/redundant_open_modes.rs @@ -1,15 +1,14 @@ use std::str::FromStr; use anyhow::{anyhow, Result}; -use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged}; -use ruff_python_parser::{lexer, Mode}; -use ruff_text_size::TextSize; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Constant, Expr, Ranged}; +use ruff_python_parser::{lexer, Mode}; use ruff_python_semantic::SemanticModel; use ruff_source_file::Locator; +use ruff_text_size::TextSize; use crate::checkers::ast::Checker; use crate::registry::Rule; @@ -64,14 +63,15 @@ impl AlwaysAutofixableViolation for RedundantOpenModes { } /// UP015 -pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { - let Some((mode_param, keywords)) = match_open(expr, checker.semantic()) else { +pub(crate) fn redundant_open_modes(checker: &mut Checker, call: &ast::ExprCall) { + if !is_open_builtin(call.func.as_ref(), checker.semantic()) { return; - }; - match mode_param { + } + + match call.arguments.find_argument("mode", 1) { None => { - if !keywords.is_empty() { - if let Some(keyword) = find_keyword(keywords, MODE_KEYWORD_ARGUMENT) { + if !call.arguments.is_empty() { + if let Some(keyword) = call.arguments.find_keyword(MODE_KEYWORD_ARGUMENT) { if let Expr::Constant(ast::ExprConstant { value: Constant::Str(mode_param_value), .. @@ -79,7 +79,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { { if let Ok(mode) = OpenMode::from_str(mode_param_value.as_str()) { checker.diagnostics.push(create_check( - expr, + call, &keyword.value, mode.replacement_value(), checker.locator(), @@ -98,7 +98,7 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { { if let Ok(mode) = OpenMode::from_str(value.as_str()) { checker.diagnostics.push(create_check( - expr, + call, mode_param, mode.replacement_value(), checker.locator(), @@ -113,29 +113,12 @@ pub(crate) fn redundant_open_modes(checker: &mut Checker, expr: &Expr) { const OPEN_FUNC_NAME: &str = "open"; const MODE_KEYWORD_ARGUMENT: &str = "mode"; -fn match_open<'a>( - expr: &'a Expr, - model: &SemanticModel, -) -> Option<(Option<&'a Expr>, &'a [Keyword])> { - let ast::ExprCall { - func, - arguments: - Arguments { - args, - keywords, - range: _, - }, - range: _, - } = expr.as_call_expr()?; - - let ast::ExprName { id, .. } = func.as_name_expr()?; - - if id.as_str() == OPEN_FUNC_NAME && model.is_builtin(id) { - // Return the "open mode" parameter and keywords. - Some((args.get(1), keywords)) - } else { - None - } +/// Returns `true` if the given `call` is a call to the `open` builtin. +fn is_open_builtin(func: &Expr, model: &SemanticModel) -> bool { + let Some(ast::ExprName { id, .. }) = func.as_name_expr() else { + return false; + }; + id.as_str() == OPEN_FUNC_NAME && model.is_builtin(id) } #[derive(Debug, Copy, Clone)] @@ -180,8 +163,8 @@ impl OpenMode { } } -fn create_check( - expr: &Expr, +fn create_check( + expr: &T, mode_param: &Expr, replacement_value: Option<&str>, locator: &Locator, @@ -208,7 +191,11 @@ fn create_check( diagnostic } -fn create_remove_param_fix(locator: &Locator, expr: &Expr, mode_param: &Expr) -> Result { +fn create_remove_param_fix( + locator: &Locator, + expr: &T, + mode_param: &Expr, +) -> Result { let content = locator.slice(expr.range()); // Find the last comma before mode_param and create a deletion fix // starting from the comma and ending after mode_param. diff --git a/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs b/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs index 2be2f1d7d1..3c863c1228 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/replace_stdout_stderr.rs @@ -2,7 +2,6 @@ use anyhow::Result; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; use ruff_python_ast::{self as ast, Keyword, Ranged}; use ruff_source_file::Locator; @@ -82,10 +81,10 @@ pub(crate) fn replace_stdout_stderr(checker: &mut Checker, call: &ast::ExprCall) .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "run"])) { // Find `stdout` and `stderr` kwargs. - let Some(stdout) = find_keyword(&call.arguments.keywords, "stdout") else { + let Some(stdout) = call.arguments.find_keyword("stdout") else { return; }; - let Some(stderr) = find_keyword(&call.arguments.keywords, "stderr") else { + let Some(stderr) = call.arguments.find_keyword("stderr") else { return; }; diff --git a/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs b/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs index c96a943909..b8938400a3 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/replace_universal_newlines.rs @@ -1,9 +1,7 @@ -use ruff_python_ast::{Expr, Keyword, Ranged}; -use ruff_text_size::{TextLen, TextRange}; - use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::find_keyword; +use ruff_python_ast::{self as ast, Ranged}; +use ruff_text_size::{TextLen, TextRange}; use crate::checkers::ast::Checker; use crate::registry::AsRule; @@ -50,13 +48,13 @@ impl AlwaysAutofixableViolation for ReplaceUniversalNewlines { } /// UP021 -pub(crate) fn replace_universal_newlines(checker: &mut Checker, func: &Expr, kwargs: &[Keyword]) { +pub(crate) fn replace_universal_newlines(checker: &mut Checker, call: &ast::ExprCall) { if checker .semantic() - .resolve_call_path(func) + .resolve_call_path(&call.func) .is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "run"])) { - let Some(kwarg) = find_keyword(kwargs, "universal_newlines") else { + let Some(kwarg) = call.arguments.find_keyword("universal_newlines") else { return; }; let range = TextRange::at(kwarg.start(), "universal_newlines".text_len()); diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 0fdd361540..82f20dcb5a 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -9,8 +9,8 @@ use ruff_text_size::TextRange; use crate::call_path::CallPath; use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor}; use crate::{ - self as ast, Arguments, Constant, ExceptHandler, Expr, Keyword, MatchCase, Parameters, Pattern, - Ranged, Stmt, TypeParam, + self as ast, Arguments, Constant, ExceptHandler, Expr, MatchCase, Parameters, Pattern, Ranged, + Stmt, TypeParam, }; /// Return `true` if the `Stmt` is a compound statement (as opposed to a simple statement). @@ -655,17 +655,6 @@ pub fn is_constant_non_singleton(expr: &Expr) -> bool { is_constant(expr) && !is_singleton(expr) } -/// Return the [`Keyword`] with the given name, if it's present in the list of -/// [`Keyword`] arguments. -/// -/// TODO(charlie): Make this an associated function on [`Arguments`]. -pub fn find_keyword<'a>(keywords: &'a [Keyword], keyword_name: &str) -> Option<&'a Keyword> { - keywords.iter().find(|keyword| { - let Keyword { arg, .. } = keyword; - arg.as_ref().is_some_and(|arg| arg == keyword_name) - }) -} - /// Return `true` if an [`Expr`] is `None`. pub const fn is_const_none(expr: &Expr) -> bool { matches!( @@ -702,14 +691,6 @@ pub const fn is_const_false(expr: &Expr) -> bool { ) } -/// Return `true` if a keyword argument is present with a non-`None` value. -pub fn has_non_none_keyword(keywords: &[Keyword], keyword: &str) -> bool { - find_keyword(keywords, keyword).is_some_and(|keyword| { - let Keyword { value, .. } = keyword; - !is_const_none(value) - }) -} - /// Extract the names of all handled exceptions. pub fn extract_handled_exceptions(handlers: &[ExceptHandler]) -> Vec<&Expr> { let mut handled_exceptions = Vec::new();