Support `.pyi` files in ruff analyze graph (#19611)

## Summary

We now return both the `.pyi` and `.py` files. Previously, we only
returned the `.pyi` file.
This commit is contained in:
Charlie Marsh 2025-07-28 22:00:27 -04:00 committed by GitHub
parent c6a123290d
commit e0f4f25d28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 39 deletions

View File

@ -57,33 +57,40 @@ fn dependencies() -> Result<()> {
.write_str(indoc::indoc! {r#" .write_str(indoc::indoc! {r#"
def f(): pass def f(): pass
"#})?; "#})?;
root.child("ruff")
.child("e.pyi")
.write_str(indoc::indoc! {r#"
def f() -> None: ...
"#})?;
insta::with_settings!({ insta::with_settings!({
filters => INSTA_FILTERS.to_vec(), filters => INSTA_FILTERS.to_vec(),
}, { }, {
assert_cmd_snapshot!(command().current_dir(&root), @r###" assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
{ {
"ruff/__init__.py": [], "ruff/__init__.py": [],
"ruff/a.py": [ "ruff/a.py": [
"ruff/b.py" "ruff/b.py"
], ],
"ruff/b.py": [ "ruff/b.py": [
"ruff/c.py" "ruff/c.py"
], ],
"ruff/c.py": [ "ruff/c.py": [
"ruff/d.py" "ruff/d.py"
], ],
"ruff/d.py": [ "ruff/d.py": [
"ruff/e.py" "ruff/e.py",
], "ruff/e.pyi"
"ruff/e.py": [] ],
} "ruff/e.py": [],
"ruff/e.pyi": []
}
----- stderr ----- ----- stderr -----
"###); "#);
}); });
Ok(()) Ok(())

View File

@ -42,13 +42,11 @@ impl ModuleImports {
// Resolve the imports. // Resolve the imports.
let mut resolved_imports = ModuleImports::default(); let mut resolved_imports = ModuleImports::default();
for import in imports { for import in imports {
let Some(resolved) = Resolver::new(db).resolve(import) else { for resolved in Resolver::new(db).resolve(import) {
continue; if let Some(path) = resolved.as_system_path() {
}; resolved_imports.insert(path.to_path_buf());
let Some(path) = resolved.as_system_path() else { }
continue; }
};
resolved_imports.insert(path.to_path_buf());
} }
Ok(resolved_imports) Ok(resolved_imports)

View File

@ -1,5 +1,5 @@
use ruff_db::files::FilePath; use ruff_db::files::FilePath;
use ty_python_semantic::resolve_module; use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
use crate::ModuleDb; use crate::ModuleDb;
use crate::collector::CollectedImport; use crate::collector::CollectedImport;
@ -16,24 +16,67 @@ impl<'a> Resolver<'a> {
} }
/// Resolve the [`CollectedImport`] into a [`FilePath`]. /// Resolve the [`CollectedImport`] into a [`FilePath`].
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> { pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
match import { match import {
CollectedImport::Import(import) => { CollectedImport::Import(import) => {
let module = resolve_module(self.db, &import)?; // Attempt to resolve the module (e.g., given `import foo`, look for `foo`).
Some(module.file(self.db)?.path(self.db)) let file = self.resolve_module(&import);
// If the file is a stub, look for the corresponding source file.
let source_file = file
.is_some_and(|file| file.extension() == Some("pyi"))
.then(|| self.resolve_real_module(&import))
.flatten();
std::iter::once(file)
.chain(std::iter::once(source_file))
.flatten()
} }
CollectedImport::ImportFrom(import) => { CollectedImport::ImportFrom(import) => {
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`). // Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
if let Some(file) = self.resolve_module(&import) {
// If the file is a stub, look for the corresponding source file.
let source_file = (file.extension() == Some("pyi"))
.then(|| self.resolve_real_module(&import))
.flatten();
return std::iter::once(Some(file))
.chain(std::iter::once(source_file))
.flatten();
}
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
let parent = import.parent(); let parent = import.parent();
let file = parent
.as_ref()
.and_then(|parent| self.resolve_module(parent));
let module = resolve_module(self.db, &import).or_else(|| { // If the file is a stub, look for the corresponding source file.
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`). let source_file = file
.is_some_and(|file| file.extension() == Some("pyi"))
.then(|| {
parent
.as_ref()
.and_then(|parent| self.resolve_real_module(parent))
})
.flatten();
resolve_module(self.db, &parent?) std::iter::once(file)
})?; .chain(std::iter::once(source_file))
.flatten()
Some(module.file(self.db)?.path(self.db))
} }
} }
} }
/// Resolves a module name to a module.
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
let module = resolve_module(self.db, module_name)?;
Some(module.file(self.db)?.path(self.db))
}
/// Resolves a module name to a module (stubs not allowed).
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
let module = resolve_real_module(self.db, module_name)?;
Some(module.file(self.db)?.path(self.db))
}
} }