Skip to content

Commit ff2d214

Browse files
authored
Don't skip over imports and other nodes containing nested statements in import collector (#13521)
1 parent 9442cd8 commit ff2d214

File tree

3 files changed

+97
-33
lines changed

3 files changed

+97
-33
lines changed

crates/ruff/tests/analyze_graph.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,58 @@ fn wildcard() -> Result<()> {
367367

368368
Ok(())
369369
}
370+
371+
#[test]
372+
fn nested_imports() -> Result<()> {
373+
let tempdir = TempDir::new()?;
374+
let root = ChildPath::new(tempdir.path());
375+
376+
root.child("ruff").child("__init__.py").write_str("")?;
377+
root.child("ruff")
378+
.child("a.py")
379+
.write_str(indoc::indoc! {r#"
380+
match x:
381+
case 1:
382+
import ruff.b
383+
"#})?;
384+
root.child("ruff")
385+
.child("b.py")
386+
.write_str(indoc::indoc! {r#"
387+
try:
388+
import ruff.c
389+
except ImportError as e:
390+
import ruff.d
391+
"#})?;
392+
root.child("ruff")
393+
.child("c.py")
394+
.write_str(indoc::indoc! {r#"def c(): ..."#})?;
395+
root.child("ruff")
396+
.child("d.py")
397+
.write_str(indoc::indoc! {r#"def d(): ..."#})?;
398+
399+
insta::with_settings!({
400+
filters => INSTA_FILTERS.to_vec(),
401+
}, {
402+
assert_cmd_snapshot!(command().current_dir(&root), @r#"
403+
success: true
404+
exit_code: 0
405+
----- stdout -----
406+
{
407+
"ruff/__init__.py": [],
408+
"ruff/a.py": [
409+
"ruff/b.py"
410+
],
411+
"ruff/b.py": [
412+
"ruff/c.py",
413+
"ruff/d.py"
414+
],
415+
"ruff/c.py": [],
416+
"ruff/d.py": []
417+
}
418+
419+
----- stderr -----
420+
"#);
421+
});
422+
423+
Ok(())
424+
}

crates/ruff_graph/src/collector.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use red_knot_python_semantic::ModuleName;
22
use ruff_python_ast::visitor::source_order::{
3-
walk_expr, walk_module, walk_stmt, SourceOrderVisitor, TraversalSignal,
3+
walk_expr, walk_module, walk_stmt, SourceOrderVisitor,
44
};
5-
use ruff_python_ast::{self as ast, AnyNodeRef, Expr, Mod, Stmt};
5+
use ruff_python_ast::{self as ast, Expr, Mod, Stmt};
66

77
/// Collect all imports for a given Python file.
88
#[derive(Default, Debug)]
@@ -32,28 +32,6 @@ impl<'a> Collector<'a> {
3232
}
3333

3434
impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
35-
fn enter_node(&mut self, node: AnyNodeRef<'ast>) -> TraversalSignal {
36-
// If string detection is enabled, we have to visit everything. Otherwise, we should only
37-
// visit compounds statements, which can contain import statements.
38-
if self.string_imports
39-
|| matches!(
40-
node,
41-
AnyNodeRef::ModModule(_)
42-
| AnyNodeRef::StmtFunctionDef(_)
43-
| AnyNodeRef::StmtClassDef(_)
44-
| AnyNodeRef::StmtWhile(_)
45-
| AnyNodeRef::StmtFor(_)
46-
| AnyNodeRef::StmtWith(_)
47-
| AnyNodeRef::StmtIf(_)
48-
| AnyNodeRef::StmtTry(_)
49-
)
50-
{
51-
TraversalSignal::Traverse
52-
} else {
53-
TraversalSignal::Skip
54-
}
55-
}
56-
5735
fn visit_stmt(&mut self, stmt: &'ast Stmt) {
5836
match stmt {
5937
Stmt::ImportFrom(ast::StmtImportFrom {
@@ -107,9 +85,38 @@ impl<'ast> SourceOrderVisitor<'ast> for Collector<'_> {
10785
}
10886
}
10987
}
110-
_ => {
88+
Stmt::FunctionDef(_)
89+
| Stmt::ClassDef(_)
90+
| Stmt::While(_)
91+
| Stmt::If(_)
92+
| Stmt::With(_)
93+
| Stmt::Match(_)
94+
| Stmt::Try(_)
95+
| Stmt::For(_) => {
96+
// Always traverse into compound statements.
11197
walk_stmt(self, stmt);
11298
}
99+
100+
Stmt::Return(_)
101+
| Stmt::Delete(_)
102+
| Stmt::Assign(_)
103+
| Stmt::AugAssign(_)
104+
| Stmt::AnnAssign(_)
105+
| Stmt::TypeAlias(_)
106+
| Stmt::Raise(_)
107+
| Stmt::Assert(_)
108+
| Stmt::Global(_)
109+
| Stmt::Nonlocal(_)
110+
| Stmt::Expr(_)
111+
| Stmt::Pass(_)
112+
| Stmt::Break(_)
113+
| Stmt::Continue(_)
114+
| Stmt::IpyEscapeCommand(_) => {
115+
// Only traverse simple statements when string imports is enabled.
116+
if self.string_imports {
117+
walk_stmt(self, stmt);
118+
}
119+
}
113120
}
114121
}
115122

crates/ruff_graph/src/lib.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
use crate::collector::Collector;
2-
pub use crate::db::ModuleDb;
3-
use crate::resolver::Resolver;
4-
pub use crate::settings::{AnalyzeSettings, Direction};
1+
use std::collections::{BTreeMap, BTreeSet};
2+
53
use anyhow::Result;
4+
65
use ruff_db::system::{SystemPath, SystemPathBuf};
76
use ruff_python_ast::helpers::to_module_path;
87
use ruff_python_parser::{parse, Mode};
9-
use serde::{Deserialize, Serialize};
10-
use std::collections::{BTreeMap, BTreeSet};
8+
9+
use crate::collector::Collector;
10+
pub use crate::db::ModuleDb;
11+
use crate::resolver::Resolver;
12+
pub use crate::settings::{AnalyzeSettings, Direction};
1113

1214
mod collector;
1315
mod db;
1416
mod resolver;
1517
mod settings;
1618

1719
#[derive(Debug, Default)]
18-
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1921
pub struct ModuleImports(BTreeSet<SystemPathBuf>);
2022

2123
impl ModuleImports {
@@ -90,7 +92,7 @@ impl ModuleImports {
9092
}
9193

9294
#[derive(Debug, Default)]
93-
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
95+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9496
pub struct ImportMap(BTreeMap<SystemPathBuf, ModuleImports>);
9597

9698
impl ImportMap {

0 commit comments

Comments
 (0)