mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
[red-knot] Improve validation for search paths (#12376)
This commit is contained in:
parent
889073578e
commit
e52be0951a
3 changed files with 212 additions and 139 deletions
|
@ -14,7 +14,7 @@ use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::state::ResolverState;
|
use crate::state::ResolverState;
|
||||||
use crate::typeshed::TypeshedVersionsQueryResult;
|
use crate::typeshed::{TypeshedVersionsParseError, TypeshedVersionsQueryResult};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
enum FilePathRef<'a> {
|
enum FilePathRef<'a> {
|
||||||
|
@ -154,6 +154,7 @@ impl ModulePathBufInner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An owned path that points to the source file for a Python module
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct ModulePathBuf(ModulePathBufInner);
|
pub(crate) struct ModulePathBuf(ModulePathBufInner);
|
||||||
|
|
||||||
|
@ -168,49 +169,6 @@ impl ModulePathBuf {
|
||||||
self.0.push(component);
|
self.0.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn extra(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
|
||||||
let path = path.into();
|
|
||||||
path.extension()
|
|
||||||
.map_or(true, |ext| matches!(ext, "py" | "pyi"))
|
|
||||||
.then_some(Self(ModulePathBufInner::Extra(path)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn first_party(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
|
||||||
let path = path.into();
|
|
||||||
path.extension()
|
|
||||||
.map_or(true, |ext| matches!(ext, "pyi" | "py"))
|
|
||||||
.then_some(Self(ModulePathBufInner::FirstParty(path)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn standard_library(path: FilePath) -> Option<Self> {
|
|
||||||
path.extension()
|
|
||||||
.map_or(true, |ext| ext == "pyi")
|
|
||||||
.then_some(Self(ModulePathBufInner::StandardLibrary(path)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn site_packages(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
|
||||||
let path = path.into();
|
|
||||||
path.extension()
|
|
||||||
.map_or(true, |ext| matches!(ext, "pyi" | "py"))
|
|
||||||
.then_some(Self(ModulePathBufInner::SitePackages(path)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn editable_installation_root(
|
|
||||||
system: &dyn System,
|
|
||||||
path: impl Into<SystemPathBuf>,
|
|
||||||
) -> Option<Self> {
|
|
||||||
let path = path.into();
|
|
||||||
// TODO: Add Salsa invalidation to this system call:
|
|
||||||
system
|
|
||||||
.is_directory(&path)
|
|
||||||
.then_some(Self(ModulePathBufInner::EditableInstall(path)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn is_regular_package(&self, search_path: &Self, resolver: &ResolverState) -> bool {
|
pub(crate) fn is_regular_package(&self, search_path: &Self, resolver: &ResolverState) -> bool {
|
||||||
ModulePathRef::from(self).is_regular_package(search_path, resolver)
|
ModulePathRef::from(self).is_regular_package(search_path, resolver)
|
||||||
|
@ -557,6 +515,7 @@ impl<'a> ModulePathRefInner<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An borrowed path that points to the source file for a Python module
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) struct ModulePathRef<'a>(ModulePathRefInner<'a>);
|
pub(crate) struct ModulePathRef<'a>(ModulePathRefInner<'a>);
|
||||||
|
|
||||||
|
@ -715,22 +674,117 @@ impl PartialEq<ModulePathRef<'_>> for VendoredPathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enumeration describing the various ways in which validation of a search path might fail.
|
||||||
|
///
|
||||||
|
/// If validation fails for a search path derived from the user settings,
|
||||||
|
/// a message must be displayed to the user,
|
||||||
|
/// as type checking cannot be done reliably in these circumstances.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) enum SearchPathValidationError {
|
||||||
|
/// The path provided by the user was not a directory
|
||||||
|
NotADirectory(SystemPathBuf),
|
||||||
|
|
||||||
|
/// The path provided by the user is a directory,
|
||||||
|
/// but no `stdlib/` subdirectory exists.
|
||||||
|
/// (This is only relevant for stdlib search paths.)
|
||||||
|
NoStdlibSubdirectory(SystemPathBuf),
|
||||||
|
|
||||||
|
/// The path provided by the user is a directory,
|
||||||
|
/// but no `stdlib/VERSIONS` file exists.
|
||||||
|
/// (This is only relevant for stdlib search paths.)
|
||||||
|
NoVersionsFile(SystemPathBuf),
|
||||||
|
|
||||||
|
/// The path provided by the user is a directory,
|
||||||
|
/// and a `stdlib/VERSIONS` file exists, but it fails to parse.
|
||||||
|
/// (This is only relevant for stdlib search paths.)
|
||||||
|
VersionsParseError(TypeshedVersionsParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SearchPathValidationError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NotADirectory(path) => write!(f, "{path} does not point to a directory"),
|
||||||
|
Self::NoStdlibSubdirectory(path) => {
|
||||||
|
write!(f, "The directory at {path} has no `stdlib/` subdirectory")
|
||||||
|
}
|
||||||
|
Self::NoVersionsFile(path) => write!(f, "Expected a file at {path}/stldib/VERSIONS"),
|
||||||
|
Self::VersionsParseError(underlying_error) => underlying_error.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for SearchPathValidationError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
if let Self::VersionsParseError(underlying_error) = self {
|
||||||
|
Some(underlying_error)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchPathResult = Result<ModuleSearchPath, SearchPathValidationError>;
|
||||||
|
|
||||||
|
/// A module-resolution search path, from which [`ModulePath`]s can be derived.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct ModuleSearchPath(Arc<ModulePathBuf>);
|
pub(crate) struct ModuleSearchPath(Arc<ModulePathBuf>);
|
||||||
|
|
||||||
impl ModuleSearchPath {
|
impl ModuleSearchPath {
|
||||||
pub(crate) fn extra(path: SystemPathBuf) -> Option<Self> {
|
pub(crate) fn extra(system: &dyn System, root: impl Into<SystemPathBuf>) -> SearchPathResult {
|
||||||
Some(Self(Arc::new(ModulePathBuf::extra(path)?)))
|
let root = root.into();
|
||||||
|
if system.is_directory(&root) {
|
||||||
|
Ok(Self(Arc::new(ModulePathBuf(ModulePathBufInner::Extra(
|
||||||
|
SystemPath::absolute(root, system.current_directory()),
|
||||||
|
)))))
|
||||||
|
} else {
|
||||||
|
Err(SearchPathValidationError::NotADirectory(root))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn first_party(path: SystemPathBuf) -> Option<Self> {
|
pub(crate) fn first_party(
|
||||||
Some(Self(Arc::new(ModulePathBuf::first_party(path)?)))
|
system: &dyn System,
|
||||||
|
root: impl Into<SystemPathBuf>,
|
||||||
|
) -> SearchPathResult {
|
||||||
|
let root = root.into();
|
||||||
|
if system.is_directory(&root) {
|
||||||
|
Ok(Self(Arc::new(ModulePathBuf(
|
||||||
|
ModulePathBufInner::FirstParty(SystemPath::absolute(
|
||||||
|
root,
|
||||||
|
system.current_directory(),
|
||||||
|
)),
|
||||||
|
))))
|
||||||
|
} else {
|
||||||
|
Err(SearchPathValidationError::NotADirectory(root))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub(crate) fn custom_stdlib(path: &SystemPath) -> Option<Self> {
|
pub(crate) fn custom_stdlib(
|
||||||
Some(Self(Arc::new(ModulePathBuf::standard_library(
|
db: &dyn Db,
|
||||||
FilePath::System(path.join("stdlib")),
|
typeshed: impl Into<SystemPathBuf>,
|
||||||
)?)))
|
) -> SearchPathResult {
|
||||||
|
let typeshed = typeshed.into();
|
||||||
|
let system = db.system();
|
||||||
|
if !system.is_directory(&typeshed) {
|
||||||
|
return Err(SearchPathValidationError::NotADirectory(typeshed));
|
||||||
|
}
|
||||||
|
let stdlib = typeshed.join("stdlib");
|
||||||
|
if !system.is_directory(&stdlib) {
|
||||||
|
return Err(SearchPathValidationError::NoStdlibSubdirectory(typeshed));
|
||||||
|
}
|
||||||
|
let Some(typeshed_versions) = system_path_to_file(db.upcast(), stdlib.join("VERSIONS"))
|
||||||
|
else {
|
||||||
|
return Err(SearchPathValidationError::NoVersionsFile(typeshed));
|
||||||
|
};
|
||||||
|
crate::typeshed::parse_typeshed_versions(db, typeshed_versions)
|
||||||
|
.as_ref()
|
||||||
|
.map_err(|validation_error| {
|
||||||
|
SearchPathValidationError::VersionsParseError(validation_error.clone())
|
||||||
|
})?;
|
||||||
|
Ok(Self(Arc::new(ModulePathBuf(
|
||||||
|
ModulePathBufInner::StandardLibrary(FilePath::System(SystemPath::absolute(
|
||||||
|
stdlib,
|
||||||
|
system.current_directory(),
|
||||||
|
))),
|
||||||
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn vendored_stdlib() -> Self {
|
pub(crate) fn vendored_stdlib() -> Self {
|
||||||
|
@ -741,14 +795,38 @@ impl ModuleSearchPath {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn site_packages(path: SystemPathBuf) -> Option<Self> {
|
pub(crate) fn site_packages(
|
||||||
Some(Self(Arc::new(ModulePathBuf::site_packages(path)?)))
|
system: &dyn System,
|
||||||
|
root: impl Into<SystemPathBuf>,
|
||||||
|
) -> SearchPathResult {
|
||||||
|
let root = root.into();
|
||||||
|
if system.is_directory(&root) {
|
||||||
|
Ok(Self(Arc::new(ModulePathBuf(
|
||||||
|
ModulePathBufInner::SitePackages(SystemPath::absolute(
|
||||||
|
root,
|
||||||
|
system.current_directory(),
|
||||||
|
)),
|
||||||
|
))))
|
||||||
|
} else {
|
||||||
|
Err(SearchPathValidationError::NotADirectory(root))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn editable(system: &dyn System, path: SystemPathBuf) -> Option<Self> {
|
pub(crate) fn editable(
|
||||||
Some(Self(Arc::new(ModulePathBuf::editable_installation_root(
|
system: &dyn System,
|
||||||
system, path,
|
root: impl Into<SystemPathBuf>,
|
||||||
)?)))
|
) -> SearchPathResult {
|
||||||
|
let root = root.into();
|
||||||
|
if system.is_directory(&root) {
|
||||||
|
Ok(Self(Arc::new(ModulePathBuf(
|
||||||
|
ModulePathBufInner::EditableInstall(SystemPath::absolute(
|
||||||
|
root,
|
||||||
|
system.current_directory(),
|
||||||
|
)),
|
||||||
|
))))
|
||||||
|
} else {
|
||||||
|
Err(SearchPathValidationError::NotADirectory(root))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_module_path(&self) -> &ModulePathBuf {
|
pub(crate) fn as_module_path(&self) -> &ModulePathBuf {
|
||||||
|
@ -808,6 +886,26 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModulePathBuf {
|
impl ModulePathBuf {
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn extra(path: impl Into<SystemPathBuf>) -> Self {
|
||||||
|
Self(ModulePathBufInner::Extra(path.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn first_party(path: impl Into<SystemPathBuf>) -> Self {
|
||||||
|
Self(ModulePathBufInner::FirstParty(path.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn standard_library(path: FilePath) -> Self {
|
||||||
|
Self(ModulePathBufInner::StandardLibrary(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn site_packages(path: impl Into<SystemPathBuf>) -> Self {
|
||||||
|
Self(ModulePathBufInner::SitePackages(path.into()))
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn join(&self, component: &str) -> Self {
|
pub(crate) fn join(&self, component: &str) -> Self {
|
||||||
ModulePathRef::from(self).join(component)
|
ModulePathRef::from(self).join(component)
|
||||||
|
@ -853,22 +951,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn constructor_rejects_non_pyi_stdlib_paths() {
|
|
||||||
assert_eq!(
|
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo.py")),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo/__init__.py")),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn path_buf_debug_impl() {
|
fn path_buf_debug_impl() {
|
||||||
assert_debug_snapshot!(
|
assert_debug_snapshot!(
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo/bar.pyi")).unwrap(),
|
ModulePathBuf::standard_library(FilePath::system("foo/bar.pyi")),
|
||||||
@r###"
|
@r###"
|
||||||
ModulePathBuf::StandardLibrary(
|
ModulePathBuf::StandardLibrary(
|
||||||
System(
|
System(
|
||||||
|
@ -894,16 +980,12 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn with_extension_methods() {
|
fn with_extension_methods() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo"))
|
ModulePathBuf::standard_library(FilePath::system("foo")).with_py_extension(),
|
||||||
.unwrap()
|
|
||||||
.with_py_extension(),
|
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo"))
|
ModulePathBuf::standard_library(FilePath::system("foo")).with_pyi_extension(),
|
||||||
.unwrap()
|
|
||||||
.with_pyi_extension(),
|
|
||||||
ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::System(
|
ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::System(
|
||||||
SystemPathBuf::from("foo.pyi")
|
SystemPathBuf::from("foo.pyi")
|
||||||
)))
|
)))
|
||||||
|
@ -911,7 +993,6 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::first_party("foo/bar")
|
ModulePathBuf::first_party("foo/bar")
|
||||||
.unwrap()
|
|
||||||
.with_py_extension()
|
.with_py_extension()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ModulePathBuf(ModulePathBufInner::FirstParty(SystemPathBuf::from(
|
ModulePathBuf(ModulePathBufInner::FirstParty(SystemPathBuf::from(
|
||||||
|
@ -991,23 +1072,19 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn join() {
|
fn join() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo"))
|
ModulePathBuf::standard_library(FilePath::system("foo")).join("bar"),
|
||||||
.unwrap()
|
|
||||||
.join("bar"),
|
|
||||||
ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::system(
|
ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::system(
|
||||||
"foo/bar"
|
"foo/bar"
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo"))
|
ModulePathBuf::standard_library(FilePath::system("foo")).join("bar.pyi"),
|
||||||
.unwrap()
|
|
||||||
.join("bar.pyi"),
|
|
||||||
ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::system(
|
ModulePathBuf(ModulePathBufInner::StandardLibrary(FilePath::system(
|
||||||
"foo/bar.pyi"
|
"foo/bar.pyi"
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::extra("foo").unwrap().join("bar.py"),
|
ModulePathBuf::extra("foo").join("bar.py"),
|
||||||
ModulePathBuf(ModulePathBufInner::Extra(SystemPathBuf::from("foo/bar.py")))
|
ModulePathBuf(ModulePathBufInner::Extra(SystemPathBuf::from("foo/bar.py")))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1015,36 +1092,30 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Extension must be `pyi`; got `py`")]
|
#[should_panic(expected = "Extension must be `pyi`; got `py`")]
|
||||||
fn stdlib_path_invalid_join_py() {
|
fn stdlib_path_invalid_join_py() {
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo"))
|
ModulePathBuf::standard_library(FilePath::system("foo")).push("bar.py");
|
||||||
.unwrap()
|
|
||||||
.push("bar.py");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Extension must be `pyi`; got `rs`")]
|
#[should_panic(expected = "Extension must be `pyi`; got `rs`")]
|
||||||
fn stdlib_path_invalid_join_rs() {
|
fn stdlib_path_invalid_join_rs() {
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo"))
|
ModulePathBuf::standard_library(FilePath::system("foo")).push("bar.rs");
|
||||||
.unwrap()
|
|
||||||
.push("bar.rs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Extension must be `py` or `pyi`; got `rs`")]
|
#[should_panic(expected = "Extension must be `py` or `pyi`; got `rs`")]
|
||||||
fn non_stdlib_path_invalid_join_rs() {
|
fn non_stdlib_path_invalid_join_rs() {
|
||||||
ModulePathBuf::site_packages("foo").unwrap().push("bar.rs");
|
ModulePathBuf::site_packages("foo").push("bar.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "already has an extension")]
|
#[should_panic(expected = "already has an extension")]
|
||||||
fn invalid_stdlib_join_too_many_extensions() {
|
fn invalid_stdlib_join_too_many_extensions() {
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo.pyi"))
|
ModulePathBuf::standard_library(FilePath::system("foo.pyi")).push("bar.pyi");
|
||||||
.unwrap()
|
|
||||||
.push("bar.pyi");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn relativize_stdlib_path_errors() {
|
fn relativize_stdlib_path_errors() {
|
||||||
let root = ModulePathBuf::standard_library(FilePath::system("foo/stdlib")).unwrap();
|
let root = ModulePathBuf::standard_library(FilePath::system("foo/stdlib"));
|
||||||
|
|
||||||
// Must have a `.pyi` extension or no extension:
|
// Must have a `.pyi` extension or no extension:
|
||||||
let bad_absolute_path = FilePath::system("foo/stdlib/x.py");
|
let bad_absolute_path = FilePath::system("foo/stdlib/x.py");
|
||||||
|
@ -1059,7 +1130,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn relativize_non_stdlib_path_errors() {
|
fn relativize_non_stdlib_path_errors() {
|
||||||
let root = ModulePathBuf::extra("foo/stdlib").unwrap();
|
let root = ModulePathBuf::extra("foo/stdlib");
|
||||||
// Must have a `.py` extension, a `.pyi` extension, or no extension:
|
// Must have a `.py` extension, a `.pyi` extension, or no extension:
|
||||||
let bad_absolute_path = FilePath::system("foo/stdlib/x.rs");
|
let bad_absolute_path = FilePath::system("foo/stdlib/x.rs");
|
||||||
assert_eq!(root.relativize_path(&bad_absolute_path), None);
|
assert_eq!(root.relativize_path(&bad_absolute_path), None);
|
||||||
|
@ -1072,7 +1143,6 @@ mod tests {
|
||||||
fn relativize_path() {
|
fn relativize_path() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModulePathBuf::standard_library(FilePath::system("foo/baz"))
|
ModulePathBuf::standard_library(FilePath::system("foo/baz"))
|
||||||
.unwrap()
|
|
||||||
.relativize_path(&FilePath::system("foo/baz/eggs/__init__.pyi"))
|
.relativize_path(&FilePath::system("foo/baz/eggs/__init__.pyi"))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ModulePathRef(ModulePathRefInner::StandardLibrary(FilePathRef::system(
|
ModulePathRef(ModulePathRefInner::StandardLibrary(FilePathRef::system(
|
||||||
|
@ -1089,7 +1159,7 @@ mod tests {
|
||||||
.with_custom_typeshed(typeshed)
|
.with_custom_typeshed(typeshed)
|
||||||
.with_target_version(target_version)
|
.with_target_version(target_version)
|
||||||
.build();
|
.build();
|
||||||
let stdlib = ModulePathBuf::standard_library(FilePath::System(stdlib)).unwrap();
|
let stdlib = ModulePathBuf::standard_library(FilePath::System(stdlib));
|
||||||
(db, stdlib)
|
(db, stdlib)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use ruff_db::system::{DirectoryEntry, System, SystemPath, SystemPathBuf};
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::module::{Module, ModuleKind};
|
use crate::module::{Module, ModuleKind};
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::path::{ModulePathBuf, ModuleSearchPath};
|
use crate::path::{ModulePathBuf, ModuleSearchPath, SearchPathValidationError};
|
||||||
use crate::state::ResolverState;
|
use crate::state::ResolverState;
|
||||||
|
|
||||||
/// Resolves a module name to a module.
|
/// Resolves a module name to a module.
|
||||||
|
@ -102,16 +102,10 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
|
||||||
///
|
///
|
||||||
/// This method also implements the typing spec's [module resolution order].
|
/// This method also implements the typing spec's [module resolution order].
|
||||||
///
|
///
|
||||||
/// TODO(Alex): this method does multiple `.unwrap()` calls when it should really return an error.
|
|
||||||
/// Each `.unwrap()` call is a point where we're validating a setting that the user would pass
|
|
||||||
/// and transforming it into an internal representation for a validated path.
|
|
||||||
/// Rather than panicking if a path fails to validate, we should display an error message to the user
|
|
||||||
/// and exit the process with a nonzero exit code.
|
|
||||||
/// This validation should probably be done outside of Salsa?
|
|
||||||
///
|
|
||||||
/// [module resolution order]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering
|
/// [module resolution order]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering
|
||||||
#[salsa::tracked(return_ref)]
|
fn try_resolve_module_resolution_settings(
|
||||||
pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSettings {
|
db: &dyn Db,
|
||||||
|
) -> Result<ModuleResolutionSettings, SearchPathValidationError> {
|
||||||
let program = Program::get(db.upcast());
|
let program = Program::get(db.upcast());
|
||||||
|
|
||||||
let SearchPathSettings {
|
let SearchPathSettings {
|
||||||
|
@ -129,30 +123,30 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
|
||||||
tracing::info!("extra search paths: {extra_paths:?}");
|
tracing::info!("extra search paths: {extra_paths:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_directory = db.system().current_directory();
|
let system = db.system();
|
||||||
|
|
||||||
let mut static_search_paths: Vec<_> = extra_paths
|
let mut static_search_paths = vec![];
|
||||||
.iter()
|
|
||||||
.map(|path| ModuleSearchPath::extra(SystemPath::absolute(path, current_directory)).unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
static_search_paths.push(
|
for path in extra_paths {
|
||||||
ModuleSearchPath::first_party(SystemPath::absolute(workspace_root, current_directory))
|
static_search_paths.push(ModuleSearchPath::extra(system, path.to_owned())?);
|
||||||
.unwrap(),
|
}
|
||||||
);
|
|
||||||
|
|
||||||
static_search_paths.push(custom_typeshed.as_ref().map_or_else(
|
static_search_paths.push(ModuleSearchPath::first_party(
|
||||||
ModuleSearchPath::vendored_stdlib,
|
system,
|
||||||
|custom| {
|
workspace_root.to_owned(),
|
||||||
ModuleSearchPath::custom_stdlib(&SystemPath::absolute(custom, current_directory))
|
)?);
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(path) = site_packages {
|
static_search_paths.push(if let Some(custom_typeshed) = custom_typeshed.as_ref() {
|
||||||
let site_packages_root =
|
ModuleSearchPath::custom_stdlib(db, custom_typeshed.to_owned())?
|
||||||
ModuleSearchPath::site_packages(SystemPath::absolute(path, current_directory)).unwrap();
|
} else {
|
||||||
static_search_paths.push(site_packages_root);
|
ModuleSearchPath::vendored_stdlib()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(site_packages) = site_packages {
|
||||||
|
static_search_paths.push(ModuleSearchPath::site_packages(
|
||||||
|
system,
|
||||||
|
site_packages.to_owned(),
|
||||||
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step
|
// TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step
|
||||||
|
@ -177,10 +171,16 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ModuleResolutionSettings {
|
Ok(ModuleResolutionSettings {
|
||||||
target_version,
|
target_version,
|
||||||
static_search_paths,
|
static_search_paths,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked(return_ref)]
|
||||||
|
pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSettings {
|
||||||
|
// TODO proper error handling if this returns an error:
|
||||||
|
try_resolve_module_resolution_settings(db).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all dynamic search paths:
|
/// Collect all dynamic search paths:
|
||||||
|
@ -319,7 +319,7 @@ impl<'db> PthFile<'db> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let possible_editable_install = SystemPath::absolute(line, site_packages);
|
let possible_editable_install = SystemPath::absolute(line, site_packages);
|
||||||
ModuleSearchPath::editable(*system, possible_editable_install)
|
ModuleSearchPath::editable(*system, possible_editable_install).ok()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1009,6 +1009,7 @@ mod tests {
|
||||||
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||||
let foo_stub = src.join("foo.pyi");
|
let foo_stub = src.join("foo.pyi");
|
||||||
|
|
||||||
|
assert_eq!(&src, foo.search_path());
|
||||||
assert_eq!(&foo_stub, foo.file().path(&db));
|
assert_eq!(&foo_stub, foo.file().path(&db));
|
||||||
|
|
||||||
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
||||||
|
@ -1165,7 +1166,8 @@ mod tests {
|
||||||
|
|
||||||
std::fs::create_dir_all(src.as_std_path())?;
|
std::fs::create_dir_all(src.as_std_path())?;
|
||||||
std::fs::create_dir_all(site_packages.as_std_path())?;
|
std::fs::create_dir_all(site_packages.as_std_path())?;
|
||||||
std::fs::create_dir_all(custom_typeshed.as_std_path())?;
|
std::fs::create_dir_all(custom_typeshed.join("stdlib").as_std_path())?;
|
||||||
|
std::fs::File::create(custom_typeshed.join("stdlib/VERSIONS").as_std_path())?;
|
||||||
|
|
||||||
std::fs::write(foo.as_std_path(), "")?;
|
std::fs::write(foo.as_std_path(), "")?;
|
||||||
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
||||||
|
@ -1659,11 +1661,10 @@ not_a_directory
|
||||||
let search_paths: Vec<&ModuleSearchPath> =
|
let search_paths: Vec<&ModuleSearchPath> =
|
||||||
module_resolution_settings(&db).search_paths(&db).collect();
|
module_resolution_settings(&db).search_paths(&db).collect();
|
||||||
|
|
||||||
assert!(search_paths
|
assert!(
|
||||||
.contains(&&ModuleSearchPath::first_party(SystemPathBuf::from("/src")).unwrap()));
|
search_paths.contains(&&ModuleSearchPath::first_party(db.system(), "/src").unwrap())
|
||||||
|
);
|
||||||
|
|
||||||
assert!(!search_paths.contains(
|
assert!(!search_paths.contains(&&ModuleSearchPath::editable(db.system(), "/src").unwrap()));
|
||||||
&&ModuleSearchPath::editable(db.system(), SystemPathBuf::from("/src")).unwrap()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,8 @@ impl<T> TestCaseBuilder<T> {
|
||||||
files: impl IntoIterator<Item = FileSpec>,
|
files: impl IntoIterator<Item = FileSpec>,
|
||||||
) -> SystemPathBuf {
|
) -> SystemPathBuf {
|
||||||
let root = location.as_ref().to_path_buf();
|
let root = location.as_ref().to_path_buf();
|
||||||
|
// Make sure to create the directory even if the list of files is empty:
|
||||||
|
db.memory_file_system().create_directory_all(&root).unwrap();
|
||||||
db.write_files(
|
db.write_files(
|
||||||
files
|
files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue