Skip to content

Commit e09bf0a

Browse files
committed
Track top-level module imports in the semantic model
1 parent ea1c089 commit e09bf0a

55 files changed

Lines changed: 394 additions & 99 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
319319
numpy::rules::numpy_2_0_deprecation(checker, expr);
320320
}
321321
if checker.enabled(Rule::DeprecatedMockImport) {
322-
pyupgrade::rules::deprecated_mock_attribute(checker, expr);
322+
pyupgrade::rules::deprecated_mock_attribute(checker, attribute);
323323
}
324324
if checker.enabled(Rule::SixPY3) {
325325
flake8_2020::rules::name_or_attribute(checker, expr);
@@ -336,7 +336,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
336336
if checker.enabled(Rule::UndocumentedWarn) {
337337
flake8_logging::rules::undocumented_warn(checker, expr);
338338
}
339-
pandas_vet::rules::attr(checker, attribute);
339+
if checker.enabled(Rule::PandasUseOfDotValues) {
340+
pandas_vet::rules::attr(checker, attribute);
341+
}
340342
}
341343
Expr::Call(
342344
call @ ast::ExprCall {
@@ -976,7 +978,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
976978
if checker.enabled(Rule::UnsortedDunderAll) {
977979
ruff::rules::sort_dunder_all_extend_call(checker, call);
978980
}
979-
980981
if checker.enabled(Rule::DefaultFactoryKwarg) {
981982
ruff::rules::default_factory_kwarg(checker, call);
982983
}

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -349,13 +349,6 @@ where
349349
}
350350
}
351351

352-
// Track each top-level import, to guide import insertions.
353-
if matches!(stmt, Stmt::Import(_) | Stmt::ImportFrom(_)) {
354-
if self.semantic.at_top_level() {
355-
self.importer.visit_import(stmt);
356-
}
357-
}
358-
359352
// Store the flags prior to any further descent, so that we can restore them after visiting
360353
// the node.
361354
let flags_snapshot = self.semantic.flags;
@@ -371,14 +364,22 @@ where
371364
self.handle_node_load(target);
372365
}
373366
Stmt::Import(ast::StmtImport { names, range: _ }) => {
367+
if self.semantic.at_top_level() {
368+
self.importer.visit_import(stmt);
369+
}
370+
374371
for alias in names {
375-
if alias.name.contains('.') && alias.asname.is_none() {
376-
// Given `import foo.bar`, `name` would be "foo", and `qualified_name` would be
377-
// "foo.bar".
378-
let name = alias.name.split('.').next().unwrap();
372+
// Given `import foo.bar`, `module` would be "foo", and `call_path` would be
373+
// `["foo", "bar"]`.
374+
let module = alias.name.split('.').next().unwrap();
375+
376+
// Mark the top-level module as "seen" by the semantic model.
377+
self.semantic.add_module(module);
378+
379+
if alias.asname.is_none() && alias.name.contains('.') {
379380
let call_path: Box<[&str]> = alias.name.split('.').collect();
380381
self.add_binding(
381-
name,
382+
module,
382383
alias.identifier(),
383384
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
384385
BindingFlags::EXTERNAL,
@@ -413,8 +414,20 @@ where
413414
level,
414415
range: _,
415416
}) => {
417+
if self.semantic.at_top_level() {
418+
self.importer.visit_import(stmt);
419+
}
420+
416421
let module = module.as_deref();
417422
let level = *level;
423+
424+
// Mark the top-level module as "seen" by the semantic model.
425+
if level.map_or(true, |level| level == 0) {
426+
if let Some(module) = module.and_then(|module| module.split('.').next()) {
427+
self.semantic.add_module(module);
428+
}
429+
}
430+
418431
for alias in names {
419432
if let Some("__future__") = module {
420433
let name = alias.asname.as_ref().unwrap_or(&alias.name);

crates/ruff_linter/src/rules/flake8_2020/rules/name_or_attribute.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use ruff_python_ast::Expr;
2-
31
use ruff_diagnostics::{Diagnostic, Violation};
42
use ruff_macros::{derive_message_formats, violation};
3+
use ruff_python_ast::Expr;
4+
use ruff_python_semantic::Modules;
55
use ruff_text_size::Ranged;
66

77
use crate::checkers::ast::Checker;
@@ -47,6 +47,10 @@ impl Violation for SixPY3 {
4747

4848
/// YTT202
4949
pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
50+
if !checker.semantic().seen_module(Modules::SIX) {
51+
return;
52+
}
53+
5054
if checker
5155
.semantic()
5256
.resolve_call_path(expr)

crates/ruff_linter/src/rules/flake8_async/rules/blocking_os_call.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use ruff_python_ast::ExprCall;
33
use ruff_diagnostics::{Diagnostic, Violation};
44
use ruff_macros::{derive_message_formats, violation};
55
use ruff_python_ast::call_path::CallPath;
6+
use ruff_python_semantic::Modules;
67
use ruff_text_size::Ranged;
78

89
use crate::checkers::ast::Checker;
@@ -42,17 +43,19 @@ impl Violation for BlockingOsCallInAsyncFunction {
4243

4344
/// ASYNC102
4445
pub(crate) fn blocking_os_call(checker: &mut Checker, call: &ExprCall) {
45-
if checker.semantic().in_async_context() {
46-
if checker
47-
.semantic()
48-
.resolve_call_path(call.func.as_ref())
49-
.as_ref()
50-
.is_some_and(is_unsafe_os_method)
51-
{
52-
checker.diagnostics.push(Diagnostic::new(
53-
BlockingOsCallInAsyncFunction,
54-
call.func.range(),
55-
));
46+
if checker.semantic().seen_module(Modules::OS) {
47+
if checker.semantic().in_async_context() {
48+
if checker
49+
.semantic()
50+
.resolve_call_path(call.func.as_ref())
51+
.as_ref()
52+
.is_some_and(is_unsafe_os_method)
53+
{
54+
checker.diagnostics.push(Diagnostic::new(
55+
BlockingOsCallInAsyncFunction,
56+
call.func.range(),
57+
));
58+
}
5659
}
5760
}
5861
}

crates/ruff_linter/src/rules/flake8_bandit/rules/bad_file_permissions.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
44
use ruff_macros::{derive_message_formats, violation};
55
use ruff_python_ast::call_path::CallPath;
66
use ruff_python_ast::{self as ast, Expr, Operator};
7-
use ruff_python_semantic::SemanticModel;
7+
use ruff_python_semantic::{Modules, SemanticModel};
88
use ruff_text_size::Ranged;
99

1010
use crate::checkers::ast::Checker;
@@ -60,6 +60,10 @@ enum Reason {
6060

6161
/// S103
6262
pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall) {
63+
if !checker.semantic().seen_module(Modules::OS) {
64+
return;
65+
}
66+
6367
if checker
6468
.semantic()
6569
.resolve_call_path(&call.func)

crates/ruff_linter/src/rules/flake8_bandit/rules/django_raw_sql.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use ruff_diagnostics::{Diagnostic, Violation};
22
use ruff_macros::{derive_message_formats, violation};
33
use ruff_python_ast::{self as ast, Expr};
4+
use ruff_python_semantic::Modules;
45
use ruff_text_size::Ranged;
56

67
use crate::checkers::ast::Checker;
@@ -35,6 +36,10 @@ impl Violation for DjangoRawSql {
3536

3637
/// S611
3738
pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
39+
if !checker.semantic().seen_module(Modules::DJANGO) {
40+
return;
41+
}
42+
3843
if checker
3944
.semantic()
4045
.resolve_call_path(&call.func)

crates/ruff_linter/src/rules/flake8_bandit/rules/logging_config_insecure_listen.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use ruff_diagnostics::{Diagnostic, Violation};
22
use ruff_macros::{derive_message_formats, violation};
33
use ruff_python_ast::{self as ast};
4+
use ruff_python_semantic::Modules;
45
use ruff_text_size::Ranged;
56

67
use crate::checkers::ast::Checker;
@@ -35,6 +36,10 @@ impl Violation for LoggingConfigInsecureListen {
3536

3637
/// S612
3738
pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::ExprCall) {
39+
if !checker.semantic().seen_module(Modules::LOGGING) {
40+
return;
41+
}
42+
3843
if checker
3944
.semantic()
4045
.resolve_call_path(&call.func)

crates/ruff_linter/src/rules/flake8_bandit/rules/tarfile_unsafe_members.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use ruff_diagnostics::Diagnostic;
33
use ruff_diagnostics::Violation;
44
use ruff_macros::{derive_message_formats, violation};
55
use ruff_python_ast::{self as ast};
6+
use ruff_python_semantic::Modules;
67
use ruff_text_size::Ranged;
78

89
/// ## What it does
@@ -48,6 +49,10 @@ impl Violation for TarfileUnsafeMembers {
4849

4950
/// S202
5051
pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall) {
52+
if !checker.semantic().seen_module(Modules::TARFILE) {
53+
return;
54+
}
55+
5156
if !call
5257
.func
5358
.as_attribute_expr()
@@ -65,10 +70,6 @@ pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall
6570
return;
6671
}
6772

68-
if !checker.semantic().seen(&["tarfile"]) {
69-
return;
70-
}
71-
7273
checker
7374
.diagnostics
7475
.push(Diagnostic::new(TarfileUnsafeMembers, call.func.range()));

crates/ruff_linter/src/rules/flake8_bugbear/rules/re_sub_positional_args.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use ruff_python_ast::{self as ast};
44

55
use ruff_diagnostics::{Diagnostic, Violation};
66
use ruff_macros::{derive_message_formats, violation};
7+
use ruff_python_semantic::Modules;
78
use ruff_text_size::Ranged;
89

910
use crate::checkers::ast::Checker;
@@ -56,6 +57,10 @@ impl Violation for ReSubPositionalArgs {
5657

5758
/// B034
5859
pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall) {
60+
if !checker.semantic().seen_module(Modules::RE) {
61+
return;
62+
}
63+
5964
let Some(method) = checker
6065
.semantic()
6166
.resolve_call_path(&call.func)

crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use ruff_text_size::TextRange;
33

44
use ruff_diagnostics::{Diagnostic, Violation};
55
use ruff_macros::{derive_message_formats, violation};
6+
use ruff_python_semantic::Modules;
67

78
use crate::checkers::ast::Checker;
89

@@ -57,6 +58,10 @@ impl Violation for CallDateFromtimestamp {
5758
}
5859

5960
pub(crate) fn call_date_fromtimestamp(checker: &mut Checker, func: &Expr, location: TextRange) {
61+
if !checker.semantic().seen_module(Modules::DATETIME) {
62+
return;
63+
}
64+
6065
if checker
6166
.semantic()
6267
.resolve_call_path(func)

0 commit comments

Comments
 (0)