mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 13:33:50 +00:00
Move find_keyword
helpers onto Arguments
struct (#6280)
## Summary Similar to #6279, moving some helpers onto the struct in the name of reducing the number of random undiscoverable utilities we have in `helpers.rs`. Most of the churn is migrating rules to take `ast::ExprCall` instead of the spread call arguments. ## Test Plan `cargo test`
This commit is contained in:
parent
041946fb64
commit
fd40864924
35 changed files with 289 additions and 468 deletions
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ShellKeyword<'a>> {
|
||||
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,
|
||||
})
|
||||
|
|
|
@ -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),
|
||||
..
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
..
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<Vec<Stmt>> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn match_properties_and_total<'a>(
|
||||
args: &'a [Expr],
|
||||
keywords: &'a [Keyword],
|
||||
) -> Result<(Vec<Stmt>, Option<&'a Keyword>)> {
|
||||
fn match_properties_and_total(arguments: &Arguments) -> Result<(Vec<Stmt>, 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}");
|
||||
|
|
|
@ -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<T: Ranged>(
|
||||
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<Edit> {
|
||||
fn create_remove_param_fix<T: Ranged>(
|
||||
locator: &Locator,
|
||||
expr: &T,
|
||||
mode_param: &Expr,
|
||||
) -> Result<Edit> {
|
||||
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.
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue