diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index f02fe6670a40a..7b0c2f7e0144d 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -319,7 +319,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { numpy::rules::numpy_2_0_deprecation(checker, expr); } if checker.enabled(Rule::DeprecatedMockImport) { - pyupgrade::rules::deprecated_mock_attribute(checker, expr); + pyupgrade::rules::deprecated_mock_attribute(checker, attribute); } if checker.enabled(Rule::SixPY3) { flake8_2020::rules::name_or_attribute(checker, expr); @@ -976,7 +976,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::UnsortedDunderAll) { ruff::rules::sort_dunder_all_extend_call(checker, call); } - if checker.enabled(Rule::DefaultFactoryKwarg) { ruff::rules::default_factory_kwarg(checker, call); } diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index d13a7a9270c0b..96a00417a503f 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -349,13 +349,6 @@ where } } - // Track each top-level import, to guide import insertions. - if matches!(stmt, Stmt::Import(_) | Stmt::ImportFrom(_)) { - if self.semantic.at_top_level() { - self.importer.visit_import(stmt); - } - } - // Store the flags prior to any further descent, so that we can restore them after visiting // the node. let flags_snapshot = self.semantic.flags; @@ -371,14 +364,22 @@ where self.handle_node_load(target); } Stmt::Import(ast::StmtImport { names, range: _ }) => { + if self.semantic.at_top_level() { + self.importer.visit_import(stmt); + } + for alias in names { - if alias.name.contains('.') && alias.asname.is_none() { - // Given `import foo.bar`, `name` would be "foo", and `qualified_name` would be - // "foo.bar". - let name = alias.name.split('.').next().unwrap(); + // Given `import foo.bar`, `module` would be "foo", and `call_path` would be + // `["foo", "bar"]`. + let module = alias.name.split('.').next().unwrap(); + + // Mark the top-level module as "seen" by the semantic model. + self.semantic.see(module); + + if alias.asname.is_none() && alias.name.contains('.') { let call_path: Box<[&str]> = alias.name.split('.').collect(); self.add_binding( - name, + module, alias.identifier(), BindingKind::SubmoduleImport(SubmoduleImport { call_path }), BindingFlags::EXTERNAL, @@ -413,8 +414,20 @@ where level, range: _, }) => { + if self.semantic.at_top_level() { + self.importer.visit_import(stmt); + } + let module = module.as_deref(); let level = *level; + + // Mark the top-level module as "seen" by the semantic model. + if level.map_or(true, |level| level == 0) { + if let Some(module) = module.and_then(|module| module.split('.').next()) { + self.semantic.see(module); + } + } + for alias in names { if let Some("__future__") = module { let name = alias.asname.as_ref().unwrap_or(&alias.name); diff --git a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs index bdd1a7d452660..5913151bcf268 100644 --- a/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs +++ b/crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs @@ -1,7 +1,7 @@ -use ruff_python_ast::Expr; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::Expr; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -47,6 +47,10 @@ impl Violation for SixPY3 { /// YTT202 pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::SIX) { + return; + } + if checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs index f5d86c0793910..19c15255e7f1c 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs @@ -3,6 +3,7 @@ use ruff_python_ast::ExprCall; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::CallPath; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -42,17 +43,19 @@ impl Violation for BlockingOsCallInAsyncFunction { /// ASYNC102 pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) { - if checker.semantic().in_async_context() { - if checker - .semantic() - .resolve_call_path(call.func.as_ref()) - .as_ref() - .is_some_and(is_unsafe_os_method) - { - checker.diagnostics.push(Diagnostic::new( - BlockingOsCallInAsyncFunction, - call.func.range(), - )); + if checker.semantic().seen(Modules::OS) { + if checker.semantic().in_async_context() { + if checker + .semantic() + .resolve_call_path(call.func.as_ref()) + .as_ref() + .is_some_and(is_unsafe_os_method) + { + checker.diagnostics.push(Diagnostic::new( + BlockingOsCallInAsyncFunction, + call.func.range(), + )); + } } } } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs index 4bc6da38c1aad..0675e2da0ad3f 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs @@ -4,7 +4,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::CallPath; use ruff_python_ast::{self as ast, Expr, Operator}; -use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -60,6 +60,10 @@ enum Reason { /// S103 pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::OS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs index 1491894d5b3ee..e5d58b123d669 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -35,6 +36,10 @@ impl Violation for DjangoRawSql { /// S611 pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs index 69814c123dae5..9351ae5d2b78c 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -35,6 +36,10 @@ impl Violation for LoggingConfigInsecureListen { /// S612 pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::LOGGING) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs index b70083c8533c4..978ae26051ee2 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs @@ -3,6 +3,7 @@ use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; /// ## What it does @@ -48,6 +49,10 @@ impl Violation for TarfileUnsafeMembers { /// S202 pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::TARFILE) { + return; + } + if !call .func .as_attribute_expr() @@ -65,10 +70,6 @@ pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall return; } - if !checker.semantic().seen(&["tarfile"]) { - return; - } - checker .diagnostics .push(Diagnostic::new(TarfileUnsafeMembers, call.func.range())); diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs index 328c9bc711efd..b93aa33a1cd8b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs @@ -4,6 +4,7 @@ use ruff_python_ast::{self as ast}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -56,6 +57,10 @@ impl Violation for ReSubPositionalArgs { /// B034 pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::RE) { + return; + } + let Some(method) = checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs index ef4e46811a1fe..3c0b56b2f415c 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs @@ -3,6 +3,7 @@ use ruff_text_size::TextRange; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; @@ -57,6 +58,10 @@ impl Violation for CallDateFromtimestamp { } pub(crate) fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, location: TextRange) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if checker .semantic() .resolve_call_path(func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs index 17b55a14d546f..f2f5c9976bd23 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs @@ -3,6 +3,7 @@ use ruff_text_size::TextRange; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; @@ -56,6 +57,10 @@ impl Violation for CallDateToday { } pub(crate) fn call_date_today(checker: &mut Checker, func: &Expr, location: TextRange) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if checker .semantic() .resolve_call_path(func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs index d7942cb6be7e8..f952a791a2bf9 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -60,6 +61,10 @@ impl Violation for CallDatetimeFromtimestamp { } pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs index b2db88e8ff009..61b9a16a0efb2 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -56,6 +57,10 @@ impl Violation for CallDatetimeNowWithoutTzinfo { } pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs index 8390fc9a7c969..90bf275d56f27 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -64,6 +65,10 @@ impl Violation for CallDatetimeStrptimeWithoutZone { /// DTZ007 pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs index d1b89c794d0de..ef26d93a98e28 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs @@ -3,6 +3,7 @@ use ruff_text_size::TextRange; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; @@ -55,6 +56,10 @@ impl Violation for CallDatetimeToday { } pub(crate) fn call_datetime_today(checker: &mut Checker, func: &Expr, location: TextRange) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs index 26536d3c5cff7..af335f7a23afc 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs @@ -3,6 +3,7 @@ use ruff_text_size::TextRange; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; @@ -63,6 +64,10 @@ pub(crate) fn call_datetime_utcfromtimestamp( func: &Expr, location: TextRange, ) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs index e9ff0dd363180..b044c24bb54dc 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs @@ -3,6 +3,7 @@ use ruff_text_size::TextRange; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; @@ -57,7 +58,12 @@ impl Violation for CallDatetimeUtcnow { } } +/// DTZ003 pub(crate) fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: TextRange) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(func) diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs index 874bfde36b625..ef93dc4f8a562 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -52,6 +53,10 @@ impl Violation for CallDatetimeWithoutTzinfo { } pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::DATETIME) { + return; + } + if !checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs b/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs index 8083575e2a59c..9fd1d11919aa0 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/all_with_model_form.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, Stmt}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -48,6 +49,10 @@ impl Violation for DjangoAllWithModelForm { /// DJ007 pub(crate) fn all_with_model_form(checker: &mut Checker, class_def: &ast::StmtClassDef) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + if !is_model_form(class_def, checker.semantic()) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs b/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs index d1211c566210a..737a6bf36a4dc 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/exclude_with_model_form.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, Stmt}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -46,6 +47,10 @@ impl Violation for DjangoExcludeWithModelForm { /// DJ006 pub(crate) fn exclude_with_model_form(checker: &mut Checker, class_def: &ast::StmtClassDef) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + if !is_model_form(class_def, checker.semantic()) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs index c46657684d236..b4fdb389dc7dd 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/locals_in_render_function.rs @@ -1,7 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; -use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -45,6 +45,10 @@ impl Violation for DjangoLocalsInRenderFunction { /// DJ003 pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + if !checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs b/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs index 68e717f3c9901..2dd0889474a00 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/model_without_dunder_str.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_python_semantic::{analyze, SemanticModel}; +use ruff_python_semantic::{analyze, Modules, SemanticModel}; use crate::checkers::ast::Checker; @@ -52,6 +52,10 @@ impl Violation for DjangoModelWithoutDunderStr { /// DJ008 pub(crate) fn model_without_dunder_str(checker: &mut Checker, class_def: &ast::StmtClassDef) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + if !is_non_abstract_model(class_def, checker.semantic()) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs index 8c7b3ab7a39d0..7a7db458de715 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/non_leading_receiver_decorator.rs @@ -2,6 +2,7 @@ use ruff_python_ast::Decorator; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -51,6 +52,10 @@ impl Violation for DjangoNonLeadingReceiverDecorator { /// DJ013 pub(crate) fn non_leading_receiver_decorator(checker: &mut Checker, decorator_list: &[Decorator]) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + let mut seen_receiver = false; for (i, decorator) in decorator_list.iter().enumerate() { let is_receiver = decorator.expression.as_call_expr().is_some_and(|call| { diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs index c1fcaac144593..10c0185b3a1bd 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/nullable_model_string_field.rs @@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::is_const_true; -use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -55,6 +55,10 @@ impl Violation for DjangoNullableModelStringField { /// DJ001 pub(crate) fn nullable_model_string_field(checker: &mut Checker, body: &[Stmt]) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + for statement in body { let Stmt::Assign(ast::StmtAssign { value, .. }) = statement else { continue; diff --git a/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs b/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs index 635527dcaf014..d03cd2e98f8ae 100644 --- a/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs +++ b/crates/ruff_linter/src/rules/flake8_django/rules/unordered_body_content_in_model.rs @@ -3,7 +3,7 @@ use std::fmt; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::{Modules, SemanticModel}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -83,6 +83,10 @@ pub(crate) fn unordered_body_content_in_model( checker: &mut Checker, class_def: &ast::StmtClassDef, ) { + if !checker.semantic().seen(Modules::DJANGO) { + return; + } + if !helpers::is_model(class_def, checker.semantic()) { return; } diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs index 552bee9c4fa5a..a0c35ea42b8d0 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/direct_logger_instantiation.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -54,6 +55,10 @@ impl Violation for DirectLoggerInstantiation { /// LOG001 pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::LOGGING) { + return; + } + if checker .semantic() .resolve_call_path(call.func.as_ref()) diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs index e7d4c8fd2ad5b..ab7c69900e839 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/invalid_get_logger_argument.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -57,6 +58,10 @@ impl Violation for InvalidGetLoggerArgument { /// LOG002 pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::LOGGING) { + return; + } + let Some(Expr::Name(expr @ ast::ExprName { id, .. })) = call.arguments.find_argument("name", 0) else { return; diff --git a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs index 134c4feef7d31..51f1ce691d4fc 100644 --- a/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs +++ b/crates/ruff_linter/src/rules/flake8_logging/rules/undocumented_warn.rs @@ -2,6 +2,7 @@ use ruff_python_ast::Expr; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -48,6 +49,10 @@ impl Violation for UndocumentedWarn { /// LOG009 pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::LOGGING) { + return; + } + if checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs index b045a443746e6..8a666b656f1db 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/collections_named_tuple.rs @@ -2,6 +2,7 @@ use ruff_python_ast::Expr; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -50,6 +51,10 @@ impl Violation for CollectionsNamedTuple { /// PYI024 pub(crate) fn collections_named_tuple(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::COLLECTIONS) { + return; + } + if checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs index 9f45a0adf80d2..97ce8e239f35c 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_expr.rs @@ -5,6 +5,7 @@ use crate::fix::snippet::SourceCodeSnippet; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_semantic::analyze::typing::is_dict; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; @@ -121,6 +122,10 @@ fn is_lowercase_allowed(env_var: &str) -> bool { /// SIM112 pub(crate) fn use_capital_environment_variables(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::OS) { + return; + } + // Ex) `os.environ['foo']` if let Expr::Subscript(_) = expr { check_os_environ_subscript(checker, expr); diff --git a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs index fe3bdbada0a91..f1d5395b1ecdb 100644 --- a/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs +++ b/crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs @@ -58,6 +58,10 @@ pub(crate) fn banned_api(checker: &mut Checker, policy: &NameMatchPol /// TID251 pub(crate) fn banned_attribute_access(checker: &mut Checker, expr: &Expr) { let banned_api = &checker.settings.flake8_tidy_imports.banned_api; + if banned_api.is_empty() { + return; + } + if let Some((banned_path, ban)) = checker .semantic() diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/async_function_with_timeout.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/async_function_with_timeout.rs index 048be94b03d5a..5c1b39f076672 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/async_function_with_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/async_function_with_timeout.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -48,15 +49,17 @@ pub(crate) fn async_function_with_timeout( if !function_def.is_async { return; } - let Some(timeout) = function_def.parameters.find("timeout") else { - return; - }; // If `trio` isn't in scope, avoid raising the diagnostic. - if !checker.semantic().seen(&["trio"]) { + if !checker.semantic().seen(Modules::TRIO) { return; } + // If the function doesn't have a `timeout` parameter, avoid raising the diagnostic. + let Some(timeout) = function_def.parameters.find("timeout") else { + return; + }; + checker.diagnostics.push(Diagnostic::new( TrioAsyncFunctionWithTimeout, timeout.range(), diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs index 5d7dc64bcbc3d..b248b67d49782 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{Expr, ExprCall}; +use ruff_python_semantic::Modules; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -51,6 +52,10 @@ impl Violation for TrioSyncCall { /// TRIO105 pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) { + if !checker.semantic().seen(Modules::TRIO) { + return; + } + let Some(method_name) = ({ let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else { return; diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs index 6870d99f1abf1..bcad6ffe77a67 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/timeout_without_await.rs @@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::AwaitVisitor; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{StmtWith, WithItem}; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; use crate::rules::flake8_trio::method_name::MethodName; @@ -49,6 +50,10 @@ pub(crate) fn timeout_without_await( with_stmt: &StmtWith, with_items: &[WithItem], ) { + if !checker.semantic().seen(Modules::TRIO) { + return; + } + let Some(method_name) = with_items.iter().find_map(|item| { let call = item.context_expr.as_call_expr()?; let call_path = checker.semantic().resolve_call_path(call.func.as_ref())?; diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs index d7387f5835d96..fe0845f14df02 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/unneeded_sleep.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, Stmt}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -42,6 +43,10 @@ impl Violation for TrioUnneededSleep { /// TRIO110 pub(crate) fn unneeded_sleep(checker: &mut Checker, while_stmt: &ast::StmtWhile) { + if !checker.semantic().seen(Modules::TRIO) { + return; + } + // The body should be a single `await` call. let [stmt] = while_stmt.body.as_slice() else { return; diff --git a/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs b/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs index a543ad5a9ae26..ae818b33004e4 100644 --- a/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs +++ b/crates/ruff_linter/src/rules/flake8_trio/rules/zero_sleep_call.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; use ruff_python_semantic::analyze::typing::find_assigned_value; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -47,6 +48,10 @@ impl AlwaysFixableViolation for TrioZeroSleepCall { /// TRIO115 pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) { + if !checker.semantic().seen(Modules::TRIO) { + return; + } + if call.arguments.len() != 1 { return; } diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs index 3cba8016e2cd6..8915f9ebdffe0 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_sep_split.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, ExprAttribute}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -53,6 +54,10 @@ impl Violation for OsSepSplit { /// PTH206 pub(crate) fn os_sep_split(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::OS) { + return; + } + let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() else { return; }; diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs index 7a106716541f9..ee226b4a438b8 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_function.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::Expr; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -55,6 +56,10 @@ impl Violation for NumpyDeprecatedFunction { /// NPY003 pub(crate) fn deprecated_function(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::NUMPY) { + return; + } + if let Some((existing, replacement)) = checker .semantic() diff --git a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs index 265a04d2ffb2f..cf99b1ec2319e 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/deprecated_type_alias.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::Expr; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -49,6 +50,10 @@ impl Violation for NumpyDeprecatedTypeAlias { /// NPY001 pub(crate) fn deprecated_type_alias(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::NUMPY) { + return; + } + if let Some(type_name) = checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs index d76507a8c709e..4d40276c6b524 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/legacy_random.rs @@ -2,6 +2,7 @@ use ruff_python_ast::Expr; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -59,6 +60,10 @@ impl Violation for NumpyLegacyRandom { /// NPY002 pub(crate) fn legacy_random(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::NUMPY) { + return; + } + if let Some(method_name) = checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs index d13773e851607..342957eee6edd 100644 --- a/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs +++ b/crates/ruff_linter/src/rules/numpy/rules/numpy_2_0_deprecation.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::Expr; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -152,6 +153,10 @@ enum Compatibility { } /// NPY201 pub(crate) fn numpy_2_0_deprecation(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::NUMPY) { + return; + } + let maybe_replacement = checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs index e59b8228a5a28..7c0ce38d00dac 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::Violation; use ruff_diagnostics::{Diagnostic, DiagnosticKind}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, ExprContext}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -45,6 +46,10 @@ impl Violation for PandasUseOfDotValues { } pub(crate) fn attr(checker: &mut Checker, attribute: &ast::ExprAttribute) { + if !checker.semantic().seen(Modules::PANDAS) { + return; + } + // Avoid, e.g., `x.values = y`. if matches!(attribute.ctx, ExprContext::Store | ExprContext::Del) { return; diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs index f7b5fa9300e6b..b07c7c7c2077d 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/read_table.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_ast::Expr; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -46,6 +47,10 @@ impl Violation for PandasUseOfDotReadTable { /// PD012 pub(crate) fn use_of_read_table(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::PANDAS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs b/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs index b9bf944821b6f..7671b0b3ef59b 100644 --- a/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs +++ b/crates/ruff_linter/src/rules/pep8_naming/rules/non_lowercase_variable_in_function.rs @@ -53,6 +53,10 @@ impl Violation for NonLowercaseVariableInFunction { /// N806 pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &Expr, name: &str) { + if str::is_lowercase(name) { + return; + } + // Ignore globals. if checker .semantic() @@ -62,6 +66,7 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &E return; } + // Ignore explicitly-allowed names. if checker .settings .pep8_naming @@ -72,10 +77,6 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &E return; } - if str::is_lowercase(name) { - return; - } - let parent = checker.semantic().current_statement(); if helpers::is_named_tuple_assignment(parent, checker.semantic()) || helpers::is_typed_dict_assignment(parent, checker.semantic()) diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs index 82984a8bc3b49..45b68cfa60b06 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_default.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -43,6 +44,10 @@ impl Violation for InvalidEnvvarDefault { /// PLW1508 pub(crate) fn invalid_envvar_default(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::OS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs index 8a77a6f9d12b0..0706b3ebee299 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_envvar_value.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -36,6 +37,10 @@ impl Violation for InvalidEnvvarValue { /// PLE1507 pub(crate) fn invalid_envvar_value(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::OS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs index bb93faa0bd2ca..04a0ba7a7c783 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_popen_preexec_fn.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -50,6 +51,10 @@ impl Violation for SubprocessPopenPreexecFn { /// PLW1509 pub(crate) fn subprocess_popen_preexec_fn(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::SUBPROCESS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs index 67b494d6bb700..2aade459df327 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/subprocess_run_without_check.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -60,6 +61,10 @@ impl AlwaysFixableViolation for SubprocessRunWithoutCheck { /// PLW1510 pub(crate) fn subprocess_run_without_check(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::SUBPROCESS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs index aed0ff290768a..a7d239b43d74a 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -4,19 +4,20 @@ use libcst_native::{ ImportNames, Name, NameOrAttribute, ParenthesizableWhitespace, }; use log::error; -use ruff_python_ast::{self as ast, Expr, Stmt}; -use crate::fix::codemods::CodegenStylist; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::whitespace::indentation; +use ruff_python_ast::{self as ast, Stmt}; use ruff_python_codegen::Stylist; +use ruff_python_semantic::Modules; use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::cst::matchers::{match_import, match_import_from, match_statement}; +use crate::fix::codemods::CodegenStylist; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum MockReference { @@ -249,23 +250,25 @@ fn format_import_from( } /// UP026 -pub(crate) fn deprecated_mock_attribute(checker: &mut Checker, expr: &Expr) { - if let Expr::Attribute(ast::ExprAttribute { value, .. }) = expr { - if collect_call_path(value) - .is_some_and(|call_path| matches!(call_path.as_slice(), ["mock", "mock"])) - { - let mut diagnostic = Diagnostic::new( - DeprecatedMockImport { - reference_type: MockReference::Attribute, - }, - value.range(), - ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - "mock".to_string(), - value.range(), - ))); - checker.diagnostics.push(diagnostic); - } +pub(crate) fn deprecated_mock_attribute(checker: &mut Checker, attribute: &ast::ExprAttribute) { + if !checker.semantic().seen(Modules::MOCK) { + return; + } + + if collect_call_path(&attribute.value) + .is_some_and(|call_path| matches!(call_path.as_slice(), ["mock", "mock"])) + { + let mut diagnostic = Diagnostic::new( + DeprecatedMockImport { + reference_type: MockReference::Attribute, + }, + attribute.value.range(), + ); + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + "mock".to_string(), + attribute.value.range(), + ))); + checker.diagnostics.push(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs index fa1f2c742786c..3290e8f900c3f 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_stdout_stderr.rs @@ -3,6 +3,7 @@ use anyhow::Result; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Keyword}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -53,6 +54,10 @@ impl Violation for ReplaceStdoutStderr { /// UP022 pub(crate) fn replace_stdout_stderr(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::SUBPROCESS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs index 9e97481dba1f6..41c40b8034ce7 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/replace_universal_newlines.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -49,6 +50,10 @@ impl AlwaysFixableViolation for ReplaceUniversalNewlines { /// UP021 pub(crate) fn replace_universal_newlines(checker: &mut Checker, call: &ast::ExprCall) { + if !checker.semantic().seen(Modules::SUBPROCESS) { + return; + } + if checker .semantic() .resolve_call_path(&call.func) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs index 86d9e6b28bf99..912907d96604e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/typing_text_str_alias.rs @@ -2,6 +2,7 @@ use ruff_python_ast::Expr; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -46,6 +47,10 @@ impl Violation for TypingTextStrAlias { /// UP019 pub(crate) fn typing_text_str_alias(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::TYPING) { + return; + } + if checker .semantic() .resolve_call_path(expr) diff --git a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs index bd3fcdd56c64a..5bcc338f8c01b 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/regex_flag_alias.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::Expr; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -52,6 +53,10 @@ impl AlwaysFixableViolation for RegexFlagAlias { /// FURB167 pub(crate) fn regex_flag_alias(checker: &mut Checker, expr: &Expr) { + if !checker.semantic().seen(Modules::RE) { + return; + } + let Some(flag) = checker .semantic() diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 39eec17a53f88..ea2bb33a36f27 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -13,7 +13,7 @@ use ruff_text_size::Ranged; use crate::analyze::type_inference::{PythonType, ResolvedPythonType}; use crate::model::SemanticModel; -use crate::{Binding, BindingKind}; +use crate::{Binding, BindingKind, Modules}; #[derive(Debug, Copy, Clone)] pub enum Callable { @@ -101,18 +101,21 @@ impl std::fmt::Display for ModuleMember { /// Returns the PEP 585 standard library generic variant for a `typing` module reference, if such /// a variant exists. pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option { - semantic.resolve_call_path(expr).and_then(|call_path| { - let [module, member] = call_path.as_slice() else { - return None; - }; - as_pep_585_generic(module, member).map(|(module, member)| { - if module.is_empty() { - ModuleMember::BuiltIn(member) - } else { - ModuleMember::Member(module, member) - } + Some(expr) + .filter(|_| semantic.seen(Modules::TYPING | Modules::TYPING_EXTENSIONS)) + .and_then(|expr| semantic.resolve_call_path(expr)) + .and_then(|call_path| { + let [module, member] = call_path.as_slice() else { + return None; + }; + as_pep_585_generic(module, member).map(|(module, member)| { + if module.is_empty() { + ModuleMember::BuiltIn(member) + } else { + ModuleMember::Member(module, member) + } + }) }) - }) } /// Return whether a given expression uses a PEP 585 standard library generic. diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 3e15e62aff7d2..963a1d5aa2ee5 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -120,6 +120,9 @@ pub struct SemanticModel<'a> { /// Flags for the semantic model. pub flags: SemanticModelFlags, + /// Modules that have been seen by the semantic model. + pub seen: Modules, + /// Exceptions that have been handled by the current scope. pub handled_exceptions: Vec, @@ -149,11 +152,41 @@ impl<'a> SemanticModel<'a> { delayed_annotations: FxHashMap::default(), rebinding_scopes: FxHashMap::default(), flags: SemanticModelFlags::new(path), + seen: Modules::empty(), handled_exceptions: Vec::default(), resolved_names: FxHashMap::default(), } } + pub fn see(&mut self, module: &str) { + match module { + "trio" => self.seen.insert(Modules::TRIO), + "numpy" => self.seen.insert(Modules::NUMPY), + "pandas" => self.seen.insert(Modules::PANDAS), + "pytest" => self.seen.insert(Modules::PYTEST), + "django" => self.seen.insert(Modules::DJANGO), + "six" => self.seen.insert(Modules::SIX), + "logging" => self.seen.insert(Modules::LOGGING), + "typing" => self.seen.insert(Modules::TYPING), + "typing_extensions" => self.seen.insert(Modules::TYPING_EXTENSIONS), + "tarfile" => self.seen.insert(Modules::TARFILE), + "re" => self.seen.insert(Modules::RE), + "collections" => self.seen.insert(Modules::COLLECTIONS), + "mock" => self.seen.insert(Modules::MOCK), + "os" => self.seen.insert(Modules::OS), + "datetime" => self.seen.insert(Modules::DATETIME), + "subprocess" => self.seen.insert(Modules::SUBPROCESS), + _ => {} + } + } + + /// Return `true` if the module at the given path was seen anywhere in the semantic model. + /// This includes both direct imports (`import trio`) and member imports (`from trio import + /// TrioTask`). + pub fn seen(&self, module: Modules) -> bool { + self.seen.intersects(module) + } + /// Return the [`Binding`] for the given [`BindingId`]. #[inline] pub fn binding(&self, id: BindingId) -> &Binding { @@ -1297,16 +1330,6 @@ impl<'a> SemanticModel<'a> { exceptions } - /// Return `true` if the module at the given path was seen anywhere in the semantic model. - /// This includes both direct imports (`import trio`) and member imports (`from trio import - /// TrioTask`). - pub fn seen(&self, module: &[&str]) -> bool { - self.bindings - .iter() - .filter_map(Binding::as_any_import) - .any(|import| import.call_path().starts_with(module)) - } - /// Generate a [`Snapshot`] of the current semantic model. pub fn snapshot(&self) -> Snapshot { Snapshot { @@ -1532,6 +1555,27 @@ impl ShadowedBinding { } } +bitflags! { + pub struct Modules: u16 { + const TRIO = 1 << 0; + const DJANGO = 1 << 1; + const NUMPY = 1 << 2; + const SIX = 1 << 3; + const PANDAS = 1 << 4; + const LOGGING = 1 << 5; + const TYPING = 1 << 6; + const TYPING_EXTENSIONS = 1 << 7; + const PYTEST = 1 << 8; + const TARFILE = 1 << 9; + const RE = 1 << 10; + const COLLECTIONS = 1 << 11; + const MOCK = 1 << 12; + const OS = 1 << 13; + const DATETIME = 1 << 14; + const SUBPROCESS = 1 << 15; + } +} + bitflags! { /// Flags indicating the current model state. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]