Skip to content

Commit e00c5b9

Browse files
committed
Support .pyi files in ruff analyze graph
1 parent 4bc34b8 commit e00c5b9

File tree

3 files changed

+87
-39
lines changed

3 files changed

+87
-39
lines changed

crates/ruff/tests/analyze_graph.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,33 +57,40 @@ fn dependencies() -> Result<()> {
5757
.write_str(indoc::indoc! {r#"
5858
def f(): pass
5959
"#})?;
60+
root.child("ruff")
61+
.child("e.pyi")
62+
.write_str(indoc::indoc! {r#"
63+
def f() -> None: ...
64+
"#})?;
6065

6166
insta::with_settings!({
6267
filters => INSTA_FILTERS.to_vec(),
6368
}, {
64-
assert_cmd_snapshot!(command().current_dir(&root), @r###"
65-
success: true
66-
exit_code: 0
67-
----- stdout -----
68-
{
69-
"ruff/__init__.py": [],
70-
"ruff/a.py": [
71-
"ruff/b.py"
72-
],
73-
"ruff/b.py": [
74-
"ruff/c.py"
75-
],
76-
"ruff/c.py": [
77-
"ruff/d.py"
78-
],
79-
"ruff/d.py": [
80-
"ruff/e.py"
81-
],
82-
"ruff/e.py": []
83-
}
69+
assert_cmd_snapshot!(command().current_dir(&root), @r#"
70+
success: true
71+
exit_code: 0
72+
----- stdout -----
73+
{
74+
"ruff/__init__.py": [],
75+
"ruff/a.py": [
76+
"ruff/b.py"
77+
],
78+
"ruff/b.py": [
79+
"ruff/c.py"
80+
],
81+
"ruff/c.py": [
82+
"ruff/d.py"
83+
],
84+
"ruff/d.py": [
85+
"ruff/e.py",
86+
"ruff/e.pyi"
87+
],
88+
"ruff/e.py": [],
89+
"ruff/e.pyi": []
90+
}
8491
85-
----- stderr -----
86-
"###);
92+
----- stderr -----
93+
"#);
8794
});
8895

8996
Ok(())

crates/ruff_graph/src/lib.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,11 @@ impl ModuleImports {
4242
// Resolve the imports.
4343
let mut resolved_imports = ModuleImports::default();
4444
for import in imports {
45-
let Some(resolved) = Resolver::new(db).resolve(import) else {
46-
continue;
47-
};
48-
let Some(path) = resolved.as_system_path() else {
49-
continue;
50-
};
51-
resolved_imports.insert(path.to_path_buf());
45+
for resolved in Resolver::new(db).resolve(import) {
46+
if let Some(path) = resolved.as_system_path() {
47+
resolved_imports.insert(path.to_path_buf());
48+
}
49+
}
5250
}
5351

5452
Ok(resolved_imports)

crates/ruff_graph/src/resolver.rs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ruff_db::files::FilePath;
2-
use ty_python_semantic::resolve_module;
2+
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
33

44
use crate::ModuleDb;
55
use crate::collector::CollectedImport;
@@ -16,24 +16,67 @@ impl<'a> Resolver<'a> {
1616
}
1717

1818
/// Resolve the [`CollectedImport`] into a [`FilePath`].
19-
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
19+
pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
2020
match import {
2121
CollectedImport::Import(import) => {
22-
let module = resolve_module(self.db, &import)?;
23-
Some(module.file(self.db)?.path(self.db))
22+
// Attempt to resolve the module (e.g., given `import foo`, look for `foo`).
23+
let file = self.resolve_module(&import);
24+
25+
// If the file is a stub, look for the corresponding source file.
26+
let source_file = file
27+
.is_some_and(|file| file.extension() == Some("pyi"))
28+
.then(|| self.resolve_real_module(&import))
29+
.flatten();
30+
31+
std::iter::once(file)
32+
.chain(std::iter::once(source_file))
33+
.flatten()
2434
}
2535
CollectedImport::ImportFrom(import) => {
2636
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
27-
let parent = import.parent();
37+
if let Some(file) = self.resolve_module(&import) {
38+
// If the file is a stub, look for the corresponding source file.
39+
let source_file = (file.extension() == Some("pyi"))
40+
.then(|| self.resolve_real_module(&import))
41+
.flatten();
2842

29-
let module = resolve_module(self.db, &import).or_else(|| {
30-
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
43+
return std::iter::once(Some(file))
44+
.chain(std::iter::once(source_file))
45+
.flatten();
46+
}
47+
48+
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
49+
let parent = import.parent();
50+
let file = parent
51+
.as_ref()
52+
.and_then(|parent| self.resolve_module(parent));
3153

32-
resolve_module(self.db, &parent?)
33-
})?;
54+
// If the file is a stub, look for the corresponding source file.
55+
let source_file = file
56+
.is_some_and(|file| file.extension() == Some("pyi"))
57+
.then(|| {
58+
parent
59+
.as_ref()
60+
.and_then(|parent| self.resolve_real_module(parent))
61+
})
62+
.flatten();
3463

35-
Some(module.file(self.db)?.path(self.db))
64+
std::iter::once(file)
65+
.chain(std::iter::once(source_file))
66+
.flatten()
3667
}
3768
}
3869
}
70+
71+
/// Resolves a module name to a module.
72+
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
73+
let module = resolve_module(self.db, module_name)?;
74+
Some(module.file(self.db)?.path(self.db))
75+
}
76+
77+
/// Resolves a module name to a module (stubs not allowed).
78+
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
79+
let module = resolve_real_module(self.db, module_name)?;
80+
Some(module.file(self.db)?.path(self.db))
81+
}
3982
}

0 commit comments

Comments
 (0)