mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-15 08:00:19 +00:00
[ty] Add completions for submodule imports
While we did previously support submodule completions via our `all_members` API, that only works when submodules are attributes of their parent module. For example, `os.path`. But that didn't work when the submodule was not an attribute of its parent. For example, `http.client`. To make the latter work, we read the directory of the parent module to discover its submodules.
This commit is contained in:
parent
948463aafa
commit
c9df4ddf6a
4 changed files with 163 additions and 8 deletions
|
@ -3,9 +3,13 @@ use std::str::FromStr;
|
|||
use std::sync::Arc;
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
|
||||
use super::path::SearchPath;
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::path::SystemOrVendoredPathRef;
|
||||
|
||||
/// Representation of a Python module.
|
||||
#[derive(Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
@ -85,6 +89,100 @@ impl Module {
|
|||
ModuleInner::NamespacePackage { .. } => ModuleKind::Package,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a list of all submodules of this module.
|
||||
///
|
||||
/// Returns an empty list if the module is not a package, if it is an empty package,
|
||||
/// or if it is a namespace package (one without an `__init__.py` or `__init__.pyi` file).
|
||||
///
|
||||
/// The names returned correspond to the "base" name of the module.
|
||||
/// That is, `{self.name}.{basename}` should give the full module name.
|
||||
pub fn all_submodules(&self, db: &dyn Db) -> Vec<Name> {
|
||||
self.all_submodules_inner(db).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn all_submodules_inner(&self, db: &dyn Db) -> Option<Vec<Name>> {
|
||||
fn is_submodule(
|
||||
is_dir: bool,
|
||||
is_file: bool,
|
||||
basename: Option<&str>,
|
||||
extension: Option<&str>,
|
||||
) -> bool {
|
||||
is_dir
|
||||
|| (is_file
|
||||
&& matches!(extension, Some("py" | "pyi"))
|
||||
&& !matches!(basename, Some("__init__.py" | "__init__.pyi")))
|
||||
}
|
||||
|
||||
// It would be complex and expensive to compute all submodules for
|
||||
// namespace packages, since a namespace package doesn't correspond
|
||||
// to a single file; it can span multiple directories across multiple
|
||||
// search paths. For now, we only compute submodules for traditional
|
||||
// packages that exist in a single directory on a single search path.
|
||||
let ModuleInner::FileModule {
|
||||
kind: ModuleKind::Package,
|
||||
file,
|
||||
..
|
||||
} = &*self.inner
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = SystemOrVendoredPathRef::try_from_file(db, *file)?;
|
||||
debug_assert!(
|
||||
matches!(path.file_name(), Some("__init__.py" | "__init__.pyi")),
|
||||
"expected package file `{:?}` to be `__init__.py` or `__init__.pyi`",
|
||||
path.file_name(),
|
||||
);
|
||||
|
||||
Some(match path.parent()? {
|
||||
SystemOrVendoredPathRef::System(parent_directory) => db
|
||||
.system()
|
||||
.read_directory(parent_directory)
|
||||
.inspect_err(|err| {
|
||||
tracing::debug!(
|
||||
"Failed to read {parent_directory:?} when looking for \
|
||||
its possible submodules: {err}"
|
||||
);
|
||||
})
|
||||
.ok()?
|
||||
.flatten()
|
||||
.filter(|entry| {
|
||||
let ty = entry.file_type();
|
||||
let path = entry.path();
|
||||
is_submodule(
|
||||
ty.is_directory(),
|
||||
ty.is_file(),
|
||||
path.file_name(),
|
||||
path.extension(),
|
||||
)
|
||||
})
|
||||
.filter_map(|entry| {
|
||||
let stem = entry.path().file_stem()?;
|
||||
is_identifier(stem).then(|| Name::from(stem))
|
||||
})
|
||||
.collect(),
|
||||
SystemOrVendoredPathRef::Vendored(parent_directory) => db
|
||||
.vendored()
|
||||
.read_directory(parent_directory)
|
||||
.into_iter()
|
||||
.filter(|entry| {
|
||||
let ty = entry.file_type();
|
||||
let path = entry.path();
|
||||
is_submodule(
|
||||
ty.is_directory(),
|
||||
ty.is_file(),
|
||||
path.file_name(),
|
||||
path.extension(),
|
||||
)
|
||||
})
|
||||
.filter_map(|entry| {
|
||||
let stem = entry.path().file_stem()?;
|
||||
is_identifier(stem).then(|| Name::from(stem))
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Module {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue