mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 07:04:53 +00:00
[red-knot] Rename FileSystem
to System
(#12214)
This commit is contained in:
parent
16a63c88cf
commit
ac04380f36
38 changed files with 1432 additions and 1291 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1888,6 +1888,7 @@ dependencies = [
|
||||||
"camino",
|
"camino",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"insta",
|
"insta",
|
||||||
|
"once_cell",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
"ruff_python_stdlib",
|
"ruff_python_stdlib",
|
||||||
|
@ -2094,7 +2095,6 @@ dependencies = [
|
||||||
"dashmap 6.0.1",
|
"dashmap 6.0.1",
|
||||||
"filetime",
|
"filetime",
|
||||||
"insta",
|
"insta",
|
||||||
"once_cell",
|
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_parser",
|
"ruff_python_parser",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use ruff_db::file_system::{FileSystemPath, FileSystemPathBuf};
|
use ruff_db::files::File;
|
||||||
use ruff_db::vfs::VfsFile;
|
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
|
|
||||||
use crate::db::Jar;
|
use crate::db::Jar;
|
||||||
|
|
||||||
|
@ -12,41 +12,41 @@ pub mod watch;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
root: FileSystemPathBuf,
|
root: SystemPathBuf,
|
||||||
/// The files that are open in the workspace.
|
/// The files that are open in the workspace.
|
||||||
///
|
///
|
||||||
/// * Editor: The files that are actively being edited in the editor (the user has a tab open with the file).
|
/// * Editor: The files that are actively being edited in the editor (the user has a tab open with the file).
|
||||||
/// * CLI: The resolved files passed as arguments to the CLI.
|
/// * CLI: The resolved files passed as arguments to the CLI.
|
||||||
open_files: FxHashSet<VfsFile>,
|
open_files: FxHashSet<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
pub fn new(root: FileSystemPathBuf) -> Self {
|
pub fn new(root: SystemPathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root,
|
root,
|
||||||
open_files: FxHashSet::default(),
|
open_files: FxHashSet::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root(&self) -> &FileSystemPath {
|
pub fn root(&self) -> &SystemPath {
|
||||||
self.root.as_path()
|
self.root.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO having the content in workspace feels wrong.
|
// TODO having the content in workspace feels wrong.
|
||||||
pub fn open_file(&mut self, file_id: VfsFile) {
|
pub fn open_file(&mut self, file_id: File) {
|
||||||
self.open_files.insert(file_id);
|
self.open_files.insert(file_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_file(&mut self, file_id: VfsFile) {
|
pub fn close_file(&mut self, file_id: File) {
|
||||||
self.open_files.remove(&file_id);
|
self.open_files.remove(&file_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO introduce an `OpenFile` type instead of using an anonymous tuple.
|
// TODO introduce an `OpenFile` type instead of using an anonymous tuple.
|
||||||
pub fn open_files(&self) -> impl Iterator<Item = VfsFile> + '_ {
|
pub fn open_files(&self) -> impl Iterator<Item = File> + '_ {
|
||||||
self.open_files.iter().copied()
|
self.open_files.iter().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_file_open(&self, file_id: VfsFile) -> bool {
|
pub fn is_file_open(&self, file_id: File) -> bool {
|
||||||
self.open_files.contains(&file_id)
|
self.open_files.contains(&file_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ use tracing::trace_span;
|
||||||
use red_knot_module_resolver::ModuleName;
|
use red_knot_module_resolver::ModuleName;
|
||||||
use red_knot_python_semantic::types::Type;
|
use red_knot_python_semantic::types::Type;
|
||||||
use red_knot_python_semantic::{HasTy, SemanticModel};
|
use red_knot_python_semantic::{HasTy, SemanticModel};
|
||||||
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::{parsed_module, ParsedModule};
|
use ruff_db::parsed::{parsed_module, ParsedModule};
|
||||||
use ruff_db::source::{source_text, SourceText};
|
use ruff_db::source::{source_text, SourceText};
|
||||||
use ruff_db::vfs::VfsFile;
|
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::visitor::{walk_stmt, Visitor};
|
use ruff_python_ast::visitor::{walk_stmt, Visitor};
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ use crate::db::Db;
|
||||||
pub(crate) fn unwind_if_cancelled(db: &dyn Db) {}
|
pub(crate) fn unwind_if_cancelled(db: &dyn Db) {}
|
||||||
|
|
||||||
#[salsa::tracked(return_ref)]
|
#[salsa::tracked(return_ref)]
|
||||||
pub(crate) fn lint_syntax(db: &dyn Db, file_id: VfsFile) -> Diagnostics {
|
pub(crate) fn lint_syntax(db: &dyn Db, file_id: File) -> Diagnostics {
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
if std::env::var("RED_KNOT_SLOW_LINT").is_ok() {
|
if std::env::var("RED_KNOT_SLOW_LINT").is_ok() {
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
|
@ -74,7 +74,7 @@ fn lint_lines(source: &str, diagnostics: &mut Vec<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[salsa::tracked(return_ref)]
|
#[salsa::tracked(return_ref)]
|
||||||
pub(crate) fn lint_semantic(db: &dyn Db, file_id: VfsFile) -> Diagnostics {
|
pub(crate) fn lint_semantic(db: &dyn Db, file_id: File) -> Diagnostics {
|
||||||
let _span = trace_span!("lint_semantic", ?file_id).entered();
|
let _span = trace_span!("lint_semantic", ?file_id).entered();
|
||||||
|
|
||||||
let source = source_text(db.upcast(), file_id);
|
let source = source_text(db.upcast(), file_id);
|
||||||
|
|
|
@ -15,8 +15,8 @@ use red_knot::Workspace;
|
||||||
use red_knot_module_resolver::{
|
use red_knot_module_resolver::{
|
||||||
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
||||||
};
|
};
|
||||||
use ruff_db::file_system::{FileSystem, FileSystemPath, OsFileSystem};
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::vfs::system_path_to_file;
|
use ruff_db::system::{OsSystem, System, SystemPath};
|
||||||
|
|
||||||
#[allow(
|
#[allow(
|
||||||
clippy::print_stdout,
|
clippy::print_stdout,
|
||||||
|
@ -35,15 +35,15 @@ pub fn main() -> anyhow::Result<()> {
|
||||||
return Err(anyhow::anyhow!("Invalid arguments"));
|
return Err(anyhow::anyhow!("Invalid arguments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let fs = OsFileSystem;
|
let system = OsSystem;
|
||||||
let entry_point = FileSystemPath::new(&arguments[1]);
|
let entry_point = SystemPath::new(&arguments[1]);
|
||||||
|
|
||||||
if !fs.exists(entry_point) {
|
if !system.path_exists(entry_point) {
|
||||||
eprintln!("The entry point does not exist.");
|
eprintln!("The entry point does not exist.");
|
||||||
return Err(anyhow::anyhow!("Invalid arguments"));
|
return Err(anyhow::anyhow!("Invalid arguments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fs.is_file(entry_point) {
|
if !system.is_file(entry_point) {
|
||||||
eprintln!("The entry point is not a file.");
|
eprintln!("The entry point is not a file.");
|
||||||
return Err(anyhow::anyhow!("Invalid arguments"));
|
return Err(anyhow::anyhow!("Invalid arguments"));
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ pub fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let workspace_search_path = workspace.root().to_path_buf();
|
let workspace_search_path = workspace.root().to_path_buf();
|
||||||
|
|
||||||
let mut program = Program::new(workspace, fs);
|
let mut program = Program::new(workspace, system);
|
||||||
|
|
||||||
set_module_resolution_settings(
|
set_module_resolution_settings(
|
||||||
&mut program,
|
&mut program,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use ruff_db::vfs::VfsFile;
|
use ruff_db::files::File;
|
||||||
use salsa::Cancelled;
|
use salsa::Cancelled;
|
||||||
|
|
||||||
use crate::lint::{lint_semantic, lint_syntax, Diagnostics};
|
use crate::lint::{lint_semantic, lint_syntax, Diagnostics};
|
||||||
|
@ -19,11 +19,11 @@ impl Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
pub fn check_file(&self, file: VfsFile) -> Result<Diagnostics, Cancelled> {
|
pub fn check_file(&self, file: File) -> Result<Diagnostics, Cancelled> {
|
||||||
self.with_db(|db| db.check_file_impl(file))
|
self.with_db(|db| db.check_file_impl(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_file_impl(&self, file: VfsFile) -> Diagnostics {
|
fn check_file_impl(&self, file: File) -> Diagnostics {
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
diagnostics.extend_from_slice(lint_syntax(self, file));
|
diagnostics.extend_from_slice(lint_syntax(self, file));
|
||||||
diagnostics.extend_from_slice(lint_semantic(self, file));
|
diagnostics.extend_from_slice(lint_semantic(self, file));
|
||||||
|
|
|
@ -3,10 +3,11 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use salsa::{Cancelled, Database};
|
use salsa::{Cancelled, Database};
|
||||||
|
|
||||||
use red_knot_module_resolver::{Db as ResolverDb, Jar as ResolverJar};
|
use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb, Jar as ResolverJar};
|
||||||
use red_knot_python_semantic::{Db as SemanticDb, Jar as SemanticJar};
|
use red_knot_python_semantic::{Db as SemanticDb, Jar as SemanticJar};
|
||||||
use ruff_db::file_system::{FileSystem, FileSystemPathBuf};
|
use ruff_db::files::{File, FilePath, Files};
|
||||||
use ruff_db::vfs::{Vfs, VfsFile, VfsPath};
|
use ruff_db::system::{System, SystemPathBuf};
|
||||||
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast};
|
use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast};
|
||||||
|
|
||||||
use crate::db::{Db, Jar};
|
use crate::db::{Db, Jar};
|
||||||
|
@ -17,20 +18,20 @@ mod check;
|
||||||
#[salsa::db(SourceJar, ResolverJar, SemanticJar, Jar)]
|
#[salsa::db(SourceJar, ResolverJar, SemanticJar, Jar)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
storage: salsa::Storage<Program>,
|
storage: salsa::Storage<Program>,
|
||||||
vfs: Vfs,
|
files: Files,
|
||||||
fs: Arc<dyn FileSystem + Send + Sync + RefUnwindSafe>,
|
system: Arc<dyn System + Send + Sync + RefUnwindSafe>,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
pub fn new<Fs>(workspace: Workspace, file_system: Fs) -> Self
|
pub fn new<S>(workspace: Workspace, system: S) -> Self
|
||||||
where
|
where
|
||||||
Fs: FileSystem + 'static + Send + Sync + RefUnwindSafe,
|
S: System + 'static + Send + Sync + RefUnwindSafe,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
storage: salsa::Storage::default(),
|
storage: salsa::Storage::default(),
|
||||||
vfs: Vfs::default(),
|
files: Files::default(),
|
||||||
fs: Arc::new(file_system),
|
system: Arc::new(system),
|
||||||
workspace,
|
workspace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ impl Program {
|
||||||
I: IntoIterator<Item = FileWatcherChange>,
|
I: IntoIterator<Item = FileWatcherChange>,
|
||||||
{
|
{
|
||||||
for change in changes {
|
for change in changes {
|
||||||
VfsFile::touch_path(self, &VfsPath::file_system(change.path));
|
File::touch_path(self, &FilePath::system(change.path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ impl Program {
|
||||||
where
|
where
|
||||||
F: FnOnce(&Program) -> T + UnwindSafe,
|
F: FnOnce(&Program) -> T + UnwindSafe,
|
||||||
{
|
{
|
||||||
// TODO: Catch in `Caancelled::catch`
|
// TODO: Catch in `Cancelled::catch`
|
||||||
// See https://salsa.zulipchat.com/#narrow/stream/145099-general/topic/How.20to.20use.20.60Cancelled.3A.3Acatch.60
|
// See https://salsa.zulipchat.com/#narrow/stream/145099-general/topic/How.20to.20use.20.60Cancelled.3A.3Acatch.60
|
||||||
Ok(f(self))
|
Ok(f(self))
|
||||||
}
|
}
|
||||||
|
@ -86,12 +87,16 @@ impl ResolverDb for Program {}
|
||||||
impl SemanticDb for Program {}
|
impl SemanticDb for Program {}
|
||||||
|
|
||||||
impl SourceDb for Program {
|
impl SourceDb for Program {
|
||||||
fn file_system(&self) -> &dyn FileSystem {
|
fn vendored(&self) -> &VendoredFileSystem {
|
||||||
&*self.fs
|
vendored_typeshed_stubs()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vfs(&self) -> &Vfs {
|
fn system(&self) -> &dyn System {
|
||||||
&self.vfs
|
&*self.system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn files(&self) -> &Files {
|
||||||
|
&self.files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,8 +108,8 @@ impl salsa::ParallelDatabase for Program {
|
||||||
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
||||||
salsa::Snapshot::new(Self {
|
salsa::Snapshot::new(Self {
|
||||||
storage: self.storage.snapshot(),
|
storage: self.storage.snapshot(),
|
||||||
vfs: self.vfs.snapshot(),
|
files: self.files.snapshot(),
|
||||||
fs: self.fs.clone(),
|
system: self.system.clone(),
|
||||||
workspace: self.workspace.clone(),
|
workspace: self.workspace.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -112,13 +117,13 @@ impl salsa::ParallelDatabase for Program {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FileWatcherChange {
|
pub struct FileWatcherChange {
|
||||||
path: FileSystemPathBuf,
|
path: SystemPathBuf,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
kind: FileChangeKind,
|
kind: FileChangeKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileWatcherChange {
|
impl FileWatcherChange {
|
||||||
pub fn new(path: FileSystemPathBuf, kind: FileChangeKind) -> Self {
|
pub fn new(path: SystemPathBuf, kind: FileChangeKind) -> Self {
|
||||||
Self { path, kind }
|
Self { path, kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::program::{FileChangeKind, FileWatcherChange};
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use notify::event::{CreateKind, RemoveKind};
|
use notify::event::{CreateKind, RemoveKind};
|
||||||
use notify::{recommended_watcher, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{recommended_watcher, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use ruff_db::file_system::FileSystemPath;
|
|
||||||
|
use ruff_db::system::SystemPath;
|
||||||
|
|
||||||
|
use crate::program::{FileChangeKind, FileWatcherChange};
|
||||||
|
|
||||||
pub struct FileWatcher {
|
pub struct FileWatcher {
|
||||||
watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
|
@ -50,7 +52,7 @@ impl FileWatcher {
|
||||||
|
|
||||||
for path in event.paths {
|
for path in event.paths {
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
if let Some(fs_path) = FileSystemPath::from_std_path(&path) {
|
if let Some(fs_path) = SystemPath::from_std_path(&path) {
|
||||||
changes.push(FileWatcherChange::new(
|
changes.push(FileWatcherChange::new(
|
||||||
fs_path.to_path_buf(),
|
fs_path.to_path_buf(),
|
||||||
change_kind,
|
change_kind,
|
||||||
|
|
|
@ -16,6 +16,7 @@ ruff_python_stdlib = { workspace = true }
|
||||||
|
|
||||||
compact_str = { workspace = true }
|
compact_str = { workspace = true }
|
||||||
camino = { workspace = true }
|
camino = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
salsa = { workspace = true }
|
salsa = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
|
@ -24,58 +24,37 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
use salsa::DebugWithDb;
|
use salsa::DebugWithDb;
|
||||||
|
|
||||||
use ruff_db::file_system::{FileSystem, FileSystemPathBuf, MemoryFileSystem, OsFileSystem};
|
use ruff_db::files::Files;
|
||||||
use ruff_db::vfs::Vfs;
|
use ruff_db::system::TestSystem;
|
||||||
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
|
|
||||||
use crate::resolver::{set_module_resolution_settings, RawModuleResolutionSettings};
|
use crate::resolver::{set_module_resolution_settings, RawModuleResolutionSettings};
|
||||||
use crate::supported_py_version::TargetVersion;
|
use crate::supported_py_version::TargetVersion;
|
||||||
|
use crate::vendored_typeshed_stubs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[salsa::db(Jar, ruff_db::Jar)]
|
#[salsa::db(Jar, ruff_db::Jar)]
|
||||||
pub(crate) struct TestDb {
|
pub(crate) struct TestDb {
|
||||||
storage: salsa::Storage<Self>,
|
storage: salsa::Storage<Self>,
|
||||||
file_system: TestFileSystem,
|
system: TestSystem,
|
||||||
|
vendored: VendoredFileSystem,
|
||||||
|
files: Files,
|
||||||
events: sync::Arc<sync::Mutex<Vec<salsa::Event>>>,
|
events: sync::Arc<sync::Mutex<Vec<salsa::Event>>>,
|
||||||
vfs: Vfs,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestDb {
|
impl TestDb {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
storage: salsa::Storage::default(),
|
storage: salsa::Storage::default(),
|
||||||
file_system: TestFileSystem::Memory(MemoryFileSystem::default()),
|
system: TestSystem::default(),
|
||||||
|
vendored: vendored_typeshed_stubs().snapshot(),
|
||||||
events: sync::Arc::default(),
|
events: sync::Arc::default(),
|
||||||
vfs: Vfs::with_stubbed_vendored(),
|
files: Files::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the memory file system.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
/// If this test db isn't using a memory file system.
|
|
||||||
pub(crate) fn memory_file_system(&self) -> &MemoryFileSystem {
|
|
||||||
if let TestFileSystem::Memory(fs) = &self.file_system {
|
|
||||||
fs
|
|
||||||
} else {
|
|
||||||
panic!("The test db is not using a memory file system");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uses the real file system instead of the memory file system.
|
|
||||||
///
|
|
||||||
/// This useful for testing advanced file system features like permissions, symlinks, etc.
|
|
||||||
///
|
|
||||||
/// Note that any files written to the memory file system won't be copied over.
|
|
||||||
pub(crate) fn with_os_file_system(&mut self) {
|
|
||||||
self.file_system = TestFileSystem::Os(OsFileSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn vfs_mut(&mut self) -> &mut Vfs {
|
|
||||||
&mut self.vfs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes the salsa events.
|
/// Takes the salsa events.
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
|
@ -103,17 +82,31 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ruff_db::Db for TestDb {
|
impl ruff_db::Db for TestDb {
|
||||||
fn file_system(&self) -> &dyn ruff_db::file_system::FileSystem {
|
fn vendored(&self) -> &VendoredFileSystem {
|
||||||
self.file_system.inner()
|
&self.vendored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vfs(&self) -> &ruff_db::vfs::Vfs {
|
fn system(&self) -> &dyn ruff_db::system::System {
|
||||||
&self.vfs
|
&self.system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn files(&self) -> &Files {
|
||||||
|
&self.files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db for TestDb {}
|
impl Db for TestDb {}
|
||||||
|
|
||||||
|
impl DbWithTestSystem for TestDb {
|
||||||
|
fn test_system(&self) -> &TestSystem {
|
||||||
|
&self.system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_system_mut(&mut self) -> &mut TestSystem {
|
||||||
|
&mut self.system
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl salsa::Database for TestDb {
|
impl salsa::Database for TestDb {
|
||||||
fn salsa_event(&self, event: salsa::Event) {
|
fn salsa_event(&self, event: salsa::Event) {
|
||||||
tracing::trace!("event: {:?}", event.debug(self));
|
tracing::trace!("event: {:?}", event.debug(self));
|
||||||
|
@ -126,40 +119,19 @@ pub(crate) mod tests {
|
||||||
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
||||||
salsa::Snapshot::new(Self {
|
salsa::Snapshot::new(Self {
|
||||||
storage: self.storage.snapshot(),
|
storage: self.storage.snapshot(),
|
||||||
file_system: self.file_system.snapshot(),
|
system: self.system.snapshot(),
|
||||||
|
vendored: self.vendored.snapshot(),
|
||||||
|
files: self.files.snapshot(),
|
||||||
events: self.events.clone(),
|
events: self.events.clone(),
|
||||||
vfs: self.vfs.snapshot(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TestFileSystem {
|
|
||||||
Memory(MemoryFileSystem),
|
|
||||||
#[allow(unused)]
|
|
||||||
Os(OsFileSystem),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestFileSystem {
|
|
||||||
fn inner(&self) -> &dyn FileSystem {
|
|
||||||
match self {
|
|
||||||
Self::Memory(inner) => inner,
|
|
||||||
Self::Os(inner) => inner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Memory(inner) => Self::Memory(inner.snapshot()),
|
|
||||||
Self::Os(inner) => Self::Os(inner.snapshot()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct TestCaseBuilder {
|
pub(crate) struct TestCaseBuilder {
|
||||||
db: TestDb,
|
db: TestDb,
|
||||||
src: FileSystemPathBuf,
|
src: SystemPathBuf,
|
||||||
custom_typeshed: FileSystemPathBuf,
|
custom_typeshed: SystemPathBuf,
|
||||||
site_packages: FileSystemPathBuf,
|
site_packages: SystemPathBuf,
|
||||||
target_version: Option<TargetVersion>,
|
target_version: Option<TargetVersion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,9 +172,9 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
pub(crate) struct TestCase {
|
pub(crate) struct TestCase {
|
||||||
pub(crate) db: TestDb,
|
pub(crate) db: TestDb,
|
||||||
pub(crate) src: FileSystemPathBuf,
|
pub(crate) src: SystemPathBuf,
|
||||||
pub(crate) custom_typeshed: FileSystemPathBuf,
|
pub(crate) custom_typeshed: SystemPathBuf,
|
||||||
pub(crate) site_packages: FileSystemPathBuf,
|
pub(crate) site_packages: SystemPathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_resolver_builder() -> std::io::Result<TestCaseBuilder> {
|
pub(crate) fn create_resolver_builder() -> std::io::Result<TestCaseBuilder> {
|
||||||
|
@ -217,9 +189,9 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let db = TestDb::new();
|
let db = TestDb::new();
|
||||||
|
|
||||||
let src = FileSystemPathBuf::from("src");
|
let src = SystemPathBuf::from("src");
|
||||||
let site_packages = FileSystemPathBuf::from("site_packages");
|
let site_packages = SystemPathBuf::from("site_packages");
|
||||||
let custom_typeshed = FileSystemPathBuf::from("typeshed");
|
let custom_typeshed = SystemPathBuf::from("typeshed");
|
||||||
|
|
||||||
let fs = db.memory_file_system();
|
let fs = db.memory_file_system();
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,6 @@ pub use module::{Module, ModuleKind};
|
||||||
pub use module_name::ModuleName;
|
pub use module_name::ModuleName;
|
||||||
pub use resolver::{resolve_module, set_module_resolution_settings, RawModuleResolutionSettings};
|
pub use resolver::{resolve_module, set_module_resolution_settings, RawModuleResolutionSettings};
|
||||||
pub use supported_py_version::TargetVersion;
|
pub use supported_py_version::TargetVersion;
|
||||||
pub use typeshed::{TypeshedVersionsParseError, TypeshedVersionsParseErrorKind};
|
pub use typeshed::{
|
||||||
|
vendored_typeshed_stubs, TypeshedVersionsParseError, TypeshedVersionsParseErrorKind,
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ruff_db::vfs::VfsFile;
|
use ruff_db::files::File;
|
||||||
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
|
@ -18,7 +18,7 @@ impl Module {
|
||||||
name: ModuleName,
|
name: ModuleName,
|
||||||
kind: ModuleKind,
|
kind: ModuleKind,
|
||||||
search_path: Arc<ModuleResolutionPathBuf>,
|
search_path: Arc<ModuleResolutionPathBuf>,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(ModuleInner {
|
inner: Arc::new(ModuleInner {
|
||||||
|
@ -36,7 +36,7 @@ impl Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The file to the source code that defines this module
|
/// The file to the source code that defines this module
|
||||||
pub fn file(&self) -> VfsFile {
|
pub fn file(&self) -> File {
|
||||||
self.inner.file
|
self.inner.file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ struct ModuleInner {
|
||||||
name: ModuleName,
|
name: ModuleName,
|
||||||
kind: ModuleKind,
|
kind: ModuleKind,
|
||||||
search_path: Arc<ModuleResolutionPathBuf>,
|
search_path: Arc<ModuleResolutionPathBuf>,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
/// Internal abstractions for differentiating between different kinds of search paths.
|
//! Internal abstractions for differentiating between different kinds of search paths.
|
||||||
///
|
//!
|
||||||
/// TODO(Alex): Should we use different types for absolute vs relative paths?
|
//! TODO(Alex): Should we use different types for absolute vs relative paths?
|
||||||
/// <https://github.com/astral-sh/ruff/pull/12141#discussion_r1667010245>
|
//! <https://github.com/astral-sh/ruff/pull/12141#discussion_r1667010245>
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use ruff_db::file_system::{FileSystemPath, FileSystemPathBuf};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::vfs::{system_path_to_file, VfsFile};
|
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
|
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::state::ResolverState;
|
use crate::state::ResolverState;
|
||||||
|
@ -20,10 +21,10 @@ use crate::typeshed::TypeshedVersionsQueryResult;
|
||||||
/// [the order given in the typing spec]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering
|
/// [the order given in the typing spec]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
enum ModuleResolutionPathBufInner {
|
enum ModuleResolutionPathBufInner {
|
||||||
Extra(FileSystemPathBuf),
|
Extra(SystemPathBuf),
|
||||||
FirstParty(FileSystemPathBuf),
|
FirstParty(SystemPathBuf),
|
||||||
StandardLibrary(FileSystemPathBuf),
|
StandardLibrary(SystemPathBuf),
|
||||||
SitePackages(FileSystemPathBuf),
|
SitePackages(SystemPathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleResolutionPathBufInner {
|
impl ModuleResolutionPathBufInner {
|
||||||
|
@ -90,7 +91,7 @@ impl ModuleResolutionPathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn extra(path: impl Into<FileSystemPathBuf>) -> Option<Self> {
|
pub(crate) fn extra(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
path.extension()
|
path.extension()
|
||||||
.map_or(true, |ext| matches!(ext, "py" | "pyi"))
|
.map_or(true, |ext| matches!(ext, "py" | "pyi"))
|
||||||
|
@ -98,7 +99,7 @@ impl ModuleResolutionPathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn first_party(path: impl Into<FileSystemPathBuf>) -> Option<Self> {
|
pub(crate) fn first_party(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
path.extension()
|
path.extension()
|
||||||
.map_or(true, |ext| matches!(ext, "pyi" | "py"))
|
.map_or(true, |ext| matches!(ext, "pyi" | "py"))
|
||||||
|
@ -106,7 +107,7 @@ impl ModuleResolutionPathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn standard_library(path: impl Into<FileSystemPathBuf>) -> Option<Self> {
|
pub(crate) fn standard_library(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
path.extension()
|
path.extension()
|
||||||
.map_or(true, |ext| ext == "pyi")
|
.map_or(true, |ext| ext == "pyi")
|
||||||
|
@ -114,12 +115,12 @@ impl ModuleResolutionPathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn stdlib_from_typeshed_root(typeshed_root: &FileSystemPath) -> Option<Self> {
|
pub(crate) fn stdlib_from_typeshed_root(typeshed_root: &SystemPath) -> Option<Self> {
|
||||||
Self::standard_library(typeshed_root.join(FileSystemPath::new("stdlib")))
|
Self::standard_library(typeshed_root.join(SystemPath::new("stdlib")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn site_packages(path: impl Into<FileSystemPathBuf>) -> Option<Self> {
|
pub(crate) fn site_packages(path: impl Into<SystemPathBuf>) -> Option<Self> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
path.extension()
|
path.extension()
|
||||||
.map_or(true, |ext| matches!(ext, "pyi" | "py"))
|
.map_or(true, |ext| matches!(ext, "pyi" | "py"))
|
||||||
|
@ -149,18 +150,14 @@ impl ModuleResolutionPathBuf {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn relativize_path<'a>(
|
pub(crate) fn relativize_path<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
absolute_path: &'a (impl AsRef<FileSystemPath> + ?Sized),
|
absolute_path: &'a (impl AsRef<SystemPath> + ?Sized),
|
||||||
) -> Option<ModuleResolutionPathRef<'a>> {
|
) -> Option<ModuleResolutionPathRef<'a>> {
|
||||||
ModuleResolutionPathRef::from(self).relativize_path(absolute_path.as_ref())
|
ModuleResolutionPathRef::from(self).relativize_path(absolute_path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory.
|
/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory.
|
||||||
pub(crate) fn to_vfs_file(
|
pub(crate) fn to_file(&self, search_path: &Self, resolver: &ResolverState) -> Option<File> {
|
||||||
&self,
|
ModuleResolutionPathRef::from(self).to_file(search_path, resolver)
|
||||||
search_path: &Self,
|
|
||||||
resolver: &ResolverState,
|
|
||||||
) -> Option<VfsFile> {
|
|
||||||
ModuleResolutionPathRef::from(self).to_vfs_file(search_path, resolver)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,18 +177,18 @@ impl fmt::Debug for ModuleResolutionPathBuf {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
enum ModuleResolutionPathRefInner<'a> {
|
enum ModuleResolutionPathRefInner<'a> {
|
||||||
Extra(&'a FileSystemPath),
|
Extra(&'a SystemPath),
|
||||||
FirstParty(&'a FileSystemPath),
|
FirstParty(&'a SystemPath),
|
||||||
StandardLibrary(&'a FileSystemPath),
|
StandardLibrary(&'a SystemPath),
|
||||||
SitePackages(&'a FileSystemPath),
|
SitePackages(&'a SystemPath),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ModuleResolutionPathRefInner<'a> {
|
impl<'a> ModuleResolutionPathRefInner<'a> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn query_stdlib_version<'db>(
|
fn query_stdlib_version<'db>(
|
||||||
module_path: &'a FileSystemPath,
|
module_path: &'a SystemPath,
|
||||||
stdlib_search_path: Self,
|
stdlib_search_path: Self,
|
||||||
stdlib_root: &FileSystemPath,
|
stdlib_root: &SystemPath,
|
||||||
resolver_state: &ResolverState<'db>,
|
resolver_state: &ResolverState<'db>,
|
||||||
) -> TypeshedVersionsQueryResult {
|
) -> TypeshedVersionsQueryResult {
|
||||||
let Some(module_name) = stdlib_search_path
|
let Some(module_name) = stdlib_search_path
|
||||||
|
@ -211,14 +208,14 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn is_directory(&self, search_path: Self, resolver: &ResolverState) -> bool {
|
fn is_directory(&self, search_path: Self, resolver: &ResolverState) -> bool {
|
||||||
match (self, search_path) {
|
match (self, search_path) {
|
||||||
(Self::Extra(path), Self::Extra(_)) => resolver.file_system().is_directory(path),
|
(Self::Extra(path), Self::Extra(_)) => resolver.system().is_directory(path),
|
||||||
(Self::FirstParty(path), Self::FirstParty(_)) => resolver.file_system().is_directory(path),
|
(Self::FirstParty(path), Self::FirstParty(_)) => resolver.system().is_directory(path),
|
||||||
(Self::SitePackages(path), Self::SitePackages(_)) => resolver.file_system().is_directory(path),
|
(Self::SitePackages(path), Self::SitePackages(_)) => resolver.system().is_directory(path),
|
||||||
(Self::StandardLibrary(path), Self::StandardLibrary(stdlib_root)) => {
|
(Self::StandardLibrary(path), Self::StandardLibrary(stdlib_root)) => {
|
||||||
match Self::query_stdlib_version( path, search_path, stdlib_root, resolver) {
|
match Self::query_stdlib_version( path, search_path, stdlib_root, resolver) {
|
||||||
TypeshedVersionsQueryResult::DoesNotExist => false,
|
TypeshedVersionsQueryResult::DoesNotExist => false,
|
||||||
TypeshedVersionsQueryResult::Exists => resolver.file_system().is_directory(path),
|
TypeshedVersionsQueryResult::Exists => resolver.system().is_directory(path),
|
||||||
TypeshedVersionsQueryResult::MaybeExists => resolver.file_system().is_directory(path),
|
TypeshedVersionsQueryResult::MaybeExists => resolver.system().is_directory(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(path, root) => unreachable!(
|
(path, root) => unreachable!(
|
||||||
|
@ -229,10 +226,10 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn is_regular_package(&self, search_path: Self, resolver: &ResolverState) -> bool {
|
fn is_regular_package(&self, search_path: Self, resolver: &ResolverState) -> bool {
|
||||||
fn is_non_stdlib_pkg(state: &ResolverState, path: &FileSystemPath) -> bool {
|
fn is_non_stdlib_pkg(state: &ResolverState, path: &SystemPath) -> bool {
|
||||||
let file_system = state.file_system();
|
let file_system = state.system();
|
||||||
file_system.exists(&path.join("__init__.py"))
|
file_system.path_exists(&path.join("__init__.py"))
|
||||||
|| file_system.exists(&path.join("__init__.pyi"))
|
|| file_system.path_exists(&path.join("__init__.pyi"))
|
||||||
}
|
}
|
||||||
|
|
||||||
match (self, search_path) {
|
match (self, search_path) {
|
||||||
|
@ -245,8 +242,8 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
|
||||||
(Self::StandardLibrary(path), Self::StandardLibrary(stdlib_root)) => {
|
(Self::StandardLibrary(path), Self::StandardLibrary(stdlib_root)) => {
|
||||||
match Self::query_stdlib_version( path, search_path, stdlib_root, resolver) {
|
match Self::query_stdlib_version( path, search_path, stdlib_root, resolver) {
|
||||||
TypeshedVersionsQueryResult::DoesNotExist => false,
|
TypeshedVersionsQueryResult::DoesNotExist => false,
|
||||||
TypeshedVersionsQueryResult::Exists => resolver.db.file_system().exists(&path.join("__init__.pyi")),
|
TypeshedVersionsQueryResult::Exists => resolver.db.system().path_exists(&path.join("__init__.pyi")),
|
||||||
TypeshedVersionsQueryResult::MaybeExists => resolver.db.file_system().exists(&path.join("__init__.pyi")),
|
TypeshedVersionsQueryResult::MaybeExists => resolver.db.system().path_exists(&path.join("__init__.pyi")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(path, root) => unreachable!(
|
(path, root) => unreachable!(
|
||||||
|
@ -255,7 +252,7 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_vfs_file(self, search_path: Self, resolver: &ResolverState) -> Option<VfsFile> {
|
fn to_file(self, search_path: Self, resolver: &ResolverState) -> Option<File> {
|
||||||
match (self, search_path) {
|
match (self, search_path) {
|
||||||
(Self::Extra(path), Self::Extra(_)) => system_path_to_file(resolver.db.upcast(), path),
|
(Self::Extra(path), Self::Extra(_)) => system_path_to_file(resolver.db.upcast(), path),
|
||||||
(Self::FirstParty(path), Self::FirstParty(_)) => system_path_to_file(resolver.db.upcast(), path),
|
(Self::FirstParty(path), Self::FirstParty(_)) => system_path_to_file(resolver.db.upcast(), path),
|
||||||
|
@ -330,7 +327,7 @@ impl<'a> ModuleResolutionPathRefInner<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn relativize_path(&self, absolute_path: &'a FileSystemPath) -> Option<Self> {
|
fn relativize_path(&self, absolute_path: &'a SystemPath) -> Option<Self> {
|
||||||
match self {
|
match self {
|
||||||
Self::Extra(root) => absolute_path.strip_prefix(root).ok().and_then(|path| {
|
Self::Extra(root) => absolute_path.strip_prefix(root).ok().and_then(|path| {
|
||||||
path.extension()
|
path.extension()
|
||||||
|
@ -379,12 +376,12 @@ impl<'a> ModuleResolutionPathRef<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn to_vfs_file(
|
pub(crate) fn to_file(
|
||||||
self,
|
self,
|
||||||
search_path: impl Into<Self>,
|
search_path: impl Into<Self>,
|
||||||
resolver: &ResolverState,
|
resolver: &ResolverState,
|
||||||
) -> Option<VfsFile> {
|
) -> Option<File> {
|
||||||
self.0.to_vfs_file(search_path.into().0, resolver)
|
self.0.to_file(search_path.into().0, resolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -403,7 +400,7 @@ impl<'a> ModuleResolutionPathRef<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn relativize_path(&self, absolute_path: &'a FileSystemPath) -> Option<Self> {
|
pub(crate) fn relativize_path(&self, absolute_path: &'a SystemPath) -> Option<Self> {
|
||||||
self.0.relativize_path(absolute_path).map(Self)
|
self.0.relativize_path(absolute_path).map(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,8 +437,8 @@ impl<'a> From<&'a ModuleResolutionPathBuf> for ModuleResolutionPathRef<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<FileSystemPath> for ModuleResolutionPathRef<'_> {
|
impl PartialEq<SystemPath> for ModuleResolutionPathRef<'_> {
|
||||||
fn eq(&self, other: &FileSystemPath) -> bool {
|
fn eq(&self, other: &SystemPath) -> bool {
|
||||||
let fs_path = match self.0 {
|
let fs_path = match self.0 {
|
||||||
ModuleResolutionPathRefInner::Extra(path) => path,
|
ModuleResolutionPathRefInner::Extra(path) => path,
|
||||||
ModuleResolutionPathRefInner::FirstParty(path) => path,
|
ModuleResolutionPathRefInner::FirstParty(path) => path,
|
||||||
|
@ -452,19 +449,19 @@ impl PartialEq<FileSystemPath> for ModuleResolutionPathRef<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<ModuleResolutionPathRef<'_>> for FileSystemPath {
|
impl PartialEq<ModuleResolutionPathRef<'_>> for SystemPath {
|
||||||
fn eq(&self, other: &ModuleResolutionPathRef) -> bool {
|
fn eq(&self, other: &ModuleResolutionPathRef) -> bool {
|
||||||
other == self
|
other == self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<FileSystemPathBuf> for ModuleResolutionPathRef<'_> {
|
impl PartialEq<SystemPathBuf> for ModuleResolutionPathRef<'_> {
|
||||||
fn eq(&self, other: &FileSystemPathBuf) -> bool {
|
fn eq(&self, other: &SystemPathBuf) -> bool {
|
||||||
self == &**other
|
self == &**other
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<ModuleResolutionPathRef<'_>> for FileSystemPathBuf {
|
impl PartialEq<ModuleResolutionPathRef<'_>> for SystemPathBuf {
|
||||||
fn eq(&self, other: &ModuleResolutionPathRef<'_>) -> bool {
|
fn eq(&self, other: &ModuleResolutionPathRef<'_>) -> bool {
|
||||||
&**self == other
|
&**self == other
|
||||||
}
|
}
|
||||||
|
@ -491,7 +488,7 @@ mod tests {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn join(
|
fn join(
|
||||||
&self,
|
&self,
|
||||||
component: &'a (impl AsRef<FileSystemPath> + ?Sized),
|
component: &'a (impl AsRef<SystemPath> + ?Sized),
|
||||||
) -> ModuleResolutionPathBuf {
|
) -> ModuleResolutionPathBuf {
|
||||||
let mut result = self.to_path_buf();
|
let mut result = self.to_path_buf();
|
||||||
result.push(component.as_ref().as_str());
|
result.push(component.as_ref().as_str());
|
||||||
|
@ -547,7 +544,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn path_ref_debug_impl() {
|
fn path_ref_debug_impl() {
|
||||||
assert_debug_snapshot!(
|
assert_debug_snapshot!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(FileSystemPath::new("foo/bar.py"))),
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(SystemPath::new("foo/bar.py"))),
|
||||||
@r###"
|
@r###"
|
||||||
ModuleResolutionPathRef::Extra(
|
ModuleResolutionPathRef::Extra(
|
||||||
"foo/bar.py",
|
"foo/bar.py",
|
||||||
|
@ -570,7 +567,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_pyi_extension(),
|
.with_pyi_extension(),
|
||||||
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary(
|
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary(
|
||||||
FileSystemPathBuf::from("foo.pyi")
|
SystemPathBuf::from("foo.pyi")
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -580,7 +577,7 @@ mod tests {
|
||||||
.with_py_extension()
|
.with_py_extension()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::FirstParty(
|
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::FirstParty(
|
||||||
FileSystemPathBuf::from("foo/bar.py")
|
SystemPathBuf::from("foo/bar.py")
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -588,25 +585,23 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_name_1_part() {
|
fn module_name_1_part() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(FileSystemPath::new(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(SystemPath::new("foo")))
|
||||||
"foo"
|
|
||||||
)))
|
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo")
|
ModuleName::new_static("foo")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary(
|
||||||
FileSystemPath::new("foo.pyi")
|
SystemPath::new("foo.pyi")
|
||||||
))
|
))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo")
|
ModuleName::new_static("foo")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::FirstParty(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::FirstParty(SystemPath::new(
|
||||||
FileSystemPath::new("foo/__init__.py")
|
"foo/__init__.py"
|
||||||
))
|
)))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo")
|
ModuleName::new_static("foo")
|
||||||
);
|
);
|
||||||
|
@ -616,14 +611,14 @@ mod tests {
|
||||||
fn module_name_2_parts() {
|
fn module_name_2_parts() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary(
|
||||||
FileSystemPath::new("foo/bar")
|
SystemPath::new("foo/bar")
|
||||||
))
|
))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo.bar")
|
ModuleName::new_static("foo.bar")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(FileSystemPath::new(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::Extra(SystemPath::new(
|
||||||
"foo/bar.pyi"
|
"foo/bar.pyi"
|
||||||
)))
|
)))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
|
@ -631,9 +626,9 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(SystemPath::new(
|
||||||
FileSystemPath::new("foo/bar/__init__.pyi")
|
"foo/bar/__init__.pyi"
|
||||||
))
|
)))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo.bar")
|
ModuleName::new_static("foo.bar")
|
||||||
);
|
);
|
||||||
|
@ -642,17 +637,17 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_name_3_parts() {
|
fn module_name_3_parts() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(SystemPath::new(
|
||||||
FileSystemPath::new("foo/bar/__init__.pyi")
|
"foo/bar/__init__.pyi"
|
||||||
))
|
)))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo.bar")
|
ModuleName::new_static("foo.bar")
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::SitePackages(SystemPath::new(
|
||||||
FileSystemPath::new("foo/bar/baz")
|
"foo/bar/baz"
|
||||||
))
|
)))
|
||||||
.to_module_name(),
|
.to_module_name(),
|
||||||
ModuleName::new_static("foo.bar.baz")
|
ModuleName::new_static("foo.bar.baz")
|
||||||
);
|
);
|
||||||
|
@ -665,7 +660,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join("bar"),
|
.join("bar"),
|
||||||
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary(
|
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary(
|
||||||
FileSystemPathBuf::from("foo/bar")
|
SystemPathBuf::from("foo/bar")
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -673,16 +668,16 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join("bar.pyi"),
|
.join("bar.pyi"),
|
||||||
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary(
|
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::StandardLibrary(
|
||||||
FileSystemPathBuf::from("foo/bar.pyi")
|
SystemPathBuf::from("foo/bar.pyi")
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModuleResolutionPathBuf::extra("foo")
|
ModuleResolutionPathBuf::extra("foo")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join("bar.py"),
|
.join("bar.py"),
|
||||||
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::Extra(
|
ModuleResolutionPathBuf(ModuleResolutionPathBufInner::Extra(SystemPathBuf::from(
|
||||||
FileSystemPathBuf::from("foo/bar.py")
|
"foo/bar.py"
|
||||||
))
|
)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,13 +718,13 @@ mod tests {
|
||||||
let root = ModuleResolutionPathBuf::standard_library("foo/stdlib").unwrap();
|
let root = ModuleResolutionPathBuf::standard_library("foo/stdlib").unwrap();
|
||||||
|
|
||||||
// Must have a `.pyi` extension or no extension:
|
// Must have a `.pyi` extension or no extension:
|
||||||
let bad_absolute_path = FileSystemPath::new("foo/stdlib/x.py");
|
let bad_absolute_path = SystemPath::new("foo/stdlib/x.py");
|
||||||
assert_eq!(root.relativize_path(bad_absolute_path), None);
|
assert_eq!(root.relativize_path(bad_absolute_path), None);
|
||||||
let second_bad_absolute_path = FileSystemPath::new("foo/stdlib/x.rs");
|
let second_bad_absolute_path = SystemPath::new("foo/stdlib/x.rs");
|
||||||
assert_eq!(root.relativize_path(second_bad_absolute_path), None);
|
assert_eq!(root.relativize_path(second_bad_absolute_path), None);
|
||||||
|
|
||||||
// Must be a path that is a child of `root`:
|
// Must be a path that is a child of `root`:
|
||||||
let third_bad_absolute_path = FileSystemPath::new("bar/stdlib/x.pyi");
|
let third_bad_absolute_path = SystemPath::new("bar/stdlib/x.pyi");
|
||||||
assert_eq!(root.relativize_path(third_bad_absolute_path), None);
|
assert_eq!(root.relativize_path(third_bad_absolute_path), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,10 +732,10 @@ mod tests {
|
||||||
fn relativize_non_stdlib_path_errors() {
|
fn relativize_non_stdlib_path_errors() {
|
||||||
let root = ModuleResolutionPathBuf::extra("foo/stdlib").unwrap();
|
let root = ModuleResolutionPathBuf::extra("foo/stdlib").unwrap();
|
||||||
// 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 = FileSystemPath::new("foo/stdlib/x.rs");
|
let bad_absolute_path = SystemPath::new("foo/stdlib/x.rs");
|
||||||
assert_eq!(root.relativize_path(bad_absolute_path), None);
|
assert_eq!(root.relativize_path(bad_absolute_path), None);
|
||||||
// Must be a path that is a child of `root`:
|
// Must be a path that is a child of `root`:
|
||||||
let second_bad_absolute_path = FileSystemPath::new("bar/stdlib/x.pyi");
|
let second_bad_absolute_path = SystemPath::new("bar/stdlib/x.pyi");
|
||||||
assert_eq!(root.relativize_path(second_bad_absolute_path), None);
|
assert_eq!(root.relativize_path(second_bad_absolute_path), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,7 +747,7 @@ mod tests {
|
||||||
.relativize_path("foo/baz/eggs/__init__.pyi")
|
.relativize_path("foo/baz/eggs/__init__.pyi")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary(
|
ModuleResolutionPathRef(ModuleResolutionPathRefInner::StandardLibrary(
|
||||||
FileSystemPath::new("eggs/__init__.pyi")
|
SystemPath::new("eggs/__init__.pyi")
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -782,21 +777,18 @@ mod tests {
|
||||||
assert!(asyncio_regular_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(asyncio_regular_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
// Paths to directories don't resolve to VfsFiles
|
// Paths to directories don't resolve to VfsFiles
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
asyncio_regular_package.to_vfs_file(&stdlib_path, &resolver),
|
asyncio_regular_package.to_file(&stdlib_path, &resolver),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
assert!(asyncio_regular_package
|
assert!(asyncio_regular_package
|
||||||
.join("__init__.pyi")
|
.join("__init__.pyi")
|
||||||
.to_vfs_file(&stdlib_path, &resolver)
|
.to_file(&stdlib_path, &resolver)
|
||||||
.is_some());
|
.is_some());
|
||||||
|
|
||||||
// The `asyncio` package exists on Python 3.8, but the `asyncio.tasks` submodule does not,
|
// The `asyncio` package exists on Python 3.8, but the `asyncio.tasks` submodule does not,
|
||||||
// according to the `VERSIONS` file in our typeshed mock:
|
// according to the `VERSIONS` file in our typeshed mock:
|
||||||
let asyncio_tasks_module = stdlib_path.join("asyncio/tasks.pyi");
|
let asyncio_tasks_module = stdlib_path.join("asyncio/tasks.pyi");
|
||||||
assert_eq!(
|
assert_eq!(asyncio_tasks_module.to_file(&stdlib_path, &resolver), None);
|
||||||
asyncio_tasks_module.to_vfs_file(&stdlib_path, &resolver),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert!(!asyncio_tasks_module.is_directory(&stdlib_path, &resolver));
|
assert!(!asyncio_tasks_module.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!asyncio_tasks_module.is_regular_package(&stdlib_path, &resolver));
|
assert!(!asyncio_tasks_module.is_regular_package(&stdlib_path, &resolver));
|
||||||
}
|
}
|
||||||
|
@ -813,15 +805,12 @@ mod tests {
|
||||||
let xml_namespace_package = stdlib_path.join("xml");
|
let xml_namespace_package = stdlib_path.join("xml");
|
||||||
assert!(xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
assert!(xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||||
// Paths to directories don't resolve to VfsFiles
|
// Paths to directories don't resolve to VfsFiles
|
||||||
assert_eq!(
|
assert_eq!(xml_namespace_package.to_file(&stdlib_path, &resolver), None);
|
||||||
xml_namespace_package.to_vfs_file(&stdlib_path, &resolver),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert!(!xml_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(!xml_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
|
||||||
let xml_etree = stdlib_path.join("xml/etree.pyi");
|
let xml_etree = stdlib_path.join("xml/etree.pyi");
|
||||||
assert!(!xml_etree.is_directory(&stdlib_path, &resolver));
|
assert!(!xml_etree.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(xml_etree.to_vfs_file(&stdlib_path, &resolver).is_some());
|
assert!(xml_etree.to_file(&stdlib_path, &resolver).is_some());
|
||||||
assert!(!xml_etree.is_regular_package(&stdlib_path, &resolver));
|
assert!(!xml_etree.is_regular_package(&stdlib_path, &resolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -835,9 +824,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let functools_module = stdlib_path.join("functools.pyi");
|
let functools_module = stdlib_path.join("functools.pyi");
|
||||||
assert!(functools_module
|
assert!(functools_module.to_file(&stdlib_path, &resolver).is_some());
|
||||||
.to_vfs_file(&stdlib_path, &resolver)
|
|
||||||
.is_some());
|
|
||||||
assert!(!functools_module.is_directory(&stdlib_path, &resolver));
|
assert!(!functools_module.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!functools_module.is_regular_package(&stdlib_path, &resolver));
|
assert!(!functools_module.is_regular_package(&stdlib_path, &resolver));
|
||||||
}
|
}
|
||||||
|
@ -853,7 +840,7 @@ mod tests {
|
||||||
|
|
||||||
let collections_regular_package = stdlib_path.join("collections");
|
let collections_regular_package = stdlib_path.join("collections");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collections_regular_package.to_vfs_file(&stdlib_path, &resolver),
|
collections_regular_package.to_file(&stdlib_path, &resolver),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
assert!(!collections_regular_package.is_directory(&stdlib_path, &resolver));
|
assert!(!collections_regular_package.is_directory(&stdlib_path, &resolver));
|
||||||
|
@ -871,14 +858,14 @@ mod tests {
|
||||||
|
|
||||||
let importlib_namespace_package = stdlib_path.join("importlib");
|
let importlib_namespace_package = stdlib_path.join("importlib");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
importlib_namespace_package.to_vfs_file(&stdlib_path, &resolver),
|
importlib_namespace_package.to_file(&stdlib_path, &resolver),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
assert!(!importlib_namespace_package.is_directory(&stdlib_path, &resolver));
|
assert!(!importlib_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
|
||||||
let importlib_abc = stdlib_path.join("importlib/abc.pyi");
|
let importlib_abc = stdlib_path.join("importlib/abc.pyi");
|
||||||
assert_eq!(importlib_abc.to_vfs_file(&stdlib_path, &resolver), None);
|
assert_eq!(importlib_abc.to_file(&stdlib_path, &resolver), None);
|
||||||
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
||||||
}
|
}
|
||||||
|
@ -893,7 +880,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let non_existent = stdlib_path.join("doesnt_even_exist");
|
let non_existent = stdlib_path.join("doesnt_even_exist");
|
||||||
assert_eq!(non_existent.to_vfs_file(&stdlib_path, &resolver), None);
|
assert_eq!(non_existent.to_file(&stdlib_path, &resolver), None);
|
||||||
assert!(!non_existent.is_directory(&stdlib_path, &resolver));
|
assert!(!non_existent.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!non_existent.is_regular_package(&stdlib_path, &resolver));
|
assert!(!non_existent.is_regular_package(&stdlib_path, &resolver));
|
||||||
}
|
}
|
||||||
|
@ -928,18 +915,18 @@ mod tests {
|
||||||
assert!(collections_regular_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(collections_regular_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
// (This is still `None`, as directories don't resolve to `Vfs` files)
|
// (This is still `None`, as directories don't resolve to `Vfs` files)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collections_regular_package.to_vfs_file(&stdlib_path, &resolver),
|
collections_regular_package.to_file(&stdlib_path, &resolver),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
assert!(collections_regular_package
|
assert!(collections_regular_package
|
||||||
.join("__init__.pyi")
|
.join("__init__.pyi")
|
||||||
.to_vfs_file(&stdlib_path, &resolver)
|
.to_file(&stdlib_path, &resolver)
|
||||||
.is_some());
|
.is_some());
|
||||||
|
|
||||||
// ...and so should the `asyncio.tasks` submodule (though it's still not a directory):
|
// ...and so should the `asyncio.tasks` submodule (though it's still not a directory):
|
||||||
let asyncio_tasks_module = stdlib_path.join("asyncio/tasks.pyi");
|
let asyncio_tasks_module = stdlib_path.join("asyncio/tasks.pyi");
|
||||||
assert!(asyncio_tasks_module
|
assert!(asyncio_tasks_module
|
||||||
.to_vfs_file(&stdlib_path, &resolver)
|
.to_file(&stdlib_path, &resolver)
|
||||||
.is_some());
|
.is_some());
|
||||||
assert!(!asyncio_tasks_module.is_directory(&stdlib_path, &resolver));
|
assert!(!asyncio_tasks_module.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!asyncio_tasks_module.is_regular_package(&stdlib_path, &resolver));
|
assert!(!asyncio_tasks_module.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
@ -960,7 +947,7 @@ mod tests {
|
||||||
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(!importlib_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
// (This is still `None`, as directories don't resolve to `Vfs` files)
|
// (This is still `None`, as directories don't resolve to `Vfs` files)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
importlib_namespace_package.to_vfs_file(&stdlib_path, &resolver),
|
importlib_namespace_package.to_file(&stdlib_path, &resolver),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -968,7 +955,7 @@ mod tests {
|
||||||
let importlib_abc = importlib_namespace_package.join("abc.pyi");
|
let importlib_abc = importlib_namespace_package.join("abc.pyi");
|
||||||
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
assert!(!importlib_abc.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
assert!(!importlib_abc.is_regular_package(&stdlib_path, &resolver));
|
||||||
assert!(importlib_abc.to_vfs_file(&stdlib_path, &resolver).is_some());
|
assert!(importlib_abc.to_file(&stdlib_path, &resolver).is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -982,15 +969,12 @@ mod tests {
|
||||||
|
|
||||||
// The `xml` package no longer exists on py39:
|
// The `xml` package no longer exists on py39:
|
||||||
let xml_namespace_package = stdlib_path.join("xml");
|
let xml_namespace_package = stdlib_path.join("xml");
|
||||||
assert_eq!(
|
assert_eq!(xml_namespace_package.to_file(&stdlib_path, &resolver), None);
|
||||||
xml_namespace_package.to_vfs_file(&stdlib_path, &resolver),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert!(!xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
assert!(!xml_namespace_package.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!xml_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
assert!(!xml_namespace_package.is_regular_package(&stdlib_path, &resolver));
|
||||||
|
|
||||||
let xml_etree = xml_namespace_package.join("etree.pyi");
|
let xml_etree = xml_namespace_package.join("etree.pyi");
|
||||||
assert_eq!(xml_etree.to_vfs_file(&stdlib_path, &resolver), None);
|
assert_eq!(xml_etree.to_file(&stdlib_path, &resolver), None);
|
||||||
assert!(!xml_etree.is_directory(&stdlib_path, &resolver));
|
assert!(!xml_etree.is_directory(&stdlib_path, &resolver));
|
||||||
assert!(!xml_etree.is_regular_package(&stdlib_path, &resolver));
|
assert!(!xml_etree.is_regular_package(&stdlib_path, &resolver));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ruff_db::file_system::FileSystemPathBuf;
|
use ruff_db::files::{File, FilePath};
|
||||||
use ruff_db::vfs::{vfs_path_to_file, VfsFile, VfsPath};
|
use ruff_db::system::SystemPathBuf;
|
||||||
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::module::{Module, ModuleKind};
|
use crate::module::{Module, ModuleKind};
|
||||||
|
@ -58,7 +58,7 @@ pub(crate) fn resolve_module_query<'db>(
|
||||||
///
|
///
|
||||||
/// Returns `None` if the path is not a module locatable via any of the known search paths.
|
/// Returns `None` if the path is not a module locatable via any of the known search paths.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) fn path_to_module(db: &dyn Db, path: &VfsPath) -> Option<Module> {
|
pub(crate) fn path_to_module(db: &dyn Db, path: &FilePath) -> Option<Module> {
|
||||||
// It's not entirely clear on first sight why this method calls `file_to_module` instead of
|
// It's not entirely clear on first sight why this method calls `file_to_module` instead of
|
||||||
// it being the other way round, considering that the first thing that `file_to_module` does
|
// it being the other way round, considering that the first thing that `file_to_module` does
|
||||||
// is to retrieve the file's path.
|
// is to retrieve the file's path.
|
||||||
|
@ -67,7 +67,7 @@ pub(crate) fn path_to_module(db: &dyn Db, path: &VfsPath) -> Option<Module> {
|
||||||
// all arguments are Salsa ingredients (something stored in Salsa). `Path`s aren't salsa ingredients but
|
// all arguments are Salsa ingredients (something stored in Salsa). `Path`s aren't salsa ingredients but
|
||||||
// `VfsFile` is. So what we do here is to retrieve the `path`'s `VfsFile` so that we can make
|
// `VfsFile` is. So what we do here is to retrieve the `path`'s `VfsFile` so that we can make
|
||||||
// use of Salsa's caching and invalidation.
|
// use of Salsa's caching and invalidation.
|
||||||
let file = vfs_path_to_file(db.upcast(), path)?;
|
let file = path.to_file(db.upcast())?;
|
||||||
file_to_module(db, file)
|
file_to_module(db, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +75,10 @@ pub(crate) fn path_to_module(db: &dyn Db, path: &VfsPath) -> Option<Module> {
|
||||||
///
|
///
|
||||||
/// Returns `None` if the file is not a module locatable via any of the known search paths.
|
/// Returns `None` if the file is not a module locatable via any of the known search paths.
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub(crate) fn file_to_module(db: &dyn Db, file: VfsFile) -> Option<Module> {
|
pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
|
||||||
let _span = tracing::trace_span!("file_to_module", ?file).entered();
|
let _span = tracing::trace_span!("file_to_module", ?file).entered();
|
||||||
|
|
||||||
let VfsPath::FileSystem(path) = file.path(db.upcast()) else {
|
let FilePath::System(path) = file.path(db.upcast()) else {
|
||||||
todo!("VendoredPaths are not yet supported")
|
todo!("VendoredPaths are not yet supported")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -120,18 +120,18 @@ pub struct RawModuleResolutionSettings {
|
||||||
/// List of user-provided paths that should take first priority in the module resolution.
|
/// List of user-provided paths that should take first priority in the module resolution.
|
||||||
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
|
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
|
||||||
/// or pyright's stubPath configuration setting.
|
/// or pyright's stubPath configuration setting.
|
||||||
pub extra_paths: Vec<FileSystemPathBuf>,
|
pub extra_paths: Vec<SystemPathBuf>,
|
||||||
|
|
||||||
/// The root of the workspace, used for finding first-party modules.
|
/// The root of the workspace, used for finding first-party modules.
|
||||||
pub workspace_root: FileSystemPathBuf,
|
pub workspace_root: SystemPathBuf,
|
||||||
|
|
||||||
/// Optional (already validated) path to standard-library typeshed stubs.
|
/// Optional (already validated) path to standard-library typeshed stubs.
|
||||||
/// If this is not provided, we will fallback to our vendored typeshed stubs
|
/// If this is not provided, we will fallback to our vendored typeshed stubs
|
||||||
/// bundled as a zip file in the binary
|
/// bundled as a zip file in the binary
|
||||||
pub custom_typeshed: Option<FileSystemPathBuf>,
|
pub custom_typeshed: Option<SystemPathBuf>,
|
||||||
|
|
||||||
/// The path to the user's `site-packages` directory, where third-party packages from ``PyPI`` are installed.
|
/// The path to the user's `site-packages` directory, where third-party packages from ``PyPI`` are installed.
|
||||||
pub site_packages: Option<FileSystemPathBuf>,
|
pub site_packages: Option<SystemPathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawModuleResolutionSettings {
|
impl RawModuleResolutionSettings {
|
||||||
|
@ -243,7 +243,7 @@ fn module_resolver_settings(db: &dyn Db) -> &ModuleResolutionSettings {
|
||||||
fn resolve_name(
|
fn resolve_name(
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
name: &ModuleName,
|
name: &ModuleName,
|
||||||
) -> Option<(Arc<ModuleResolutionPathBuf>, VfsFile, ModuleKind)> {
|
) -> Option<(Arc<ModuleResolutionPathBuf>, File, ModuleKind)> {
|
||||||
let resolver_settings = module_resolver_settings(db);
|
let resolver_settings = module_resolver_settings(db);
|
||||||
let resolver_state = ResolverState::new(db, resolver_settings.target_version());
|
let resolver_state = ResolverState::new(db, resolver_settings.target_version());
|
||||||
|
|
||||||
|
@ -268,14 +268,14 @@ fn resolve_name(
|
||||||
// TODO Implement full https://peps.python.org/pep-0561/#type-checker-module-resolution-order resolution
|
// TODO Implement full https://peps.python.org/pep-0561/#type-checker-module-resolution-order resolution
|
||||||
if let Some(stub) = package_path
|
if let Some(stub) = package_path
|
||||||
.with_pyi_extension()
|
.with_pyi_extension()
|
||||||
.to_vfs_file(search_path, &resolver_state)
|
.to_file(search_path, &resolver_state)
|
||||||
{
|
{
|
||||||
return Some((search_path.clone(), stub, kind));
|
return Some((search_path.clone(), stub, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(module) = package_path
|
if let Some(module) = package_path
|
||||||
.with_py_extension()
|
.with_py_extension()
|
||||||
.and_then(|path| path.to_vfs_file(search_path, &resolver_state))
|
.and_then(|path| path.to_file(search_path, &resolver_state))
|
||||||
{
|
{
|
||||||
return Some((search_path.clone(), module, kind));
|
return Some((search_path.clone(), module, kind));
|
||||||
}
|
}
|
||||||
|
@ -386,8 +386,8 @@ impl PackageKind {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ruff_db::file_system::FileSystemPath;
|
use ruff_db::files::{system_path_to_file, File, FilePath};
|
||||||
use ruff_db::vfs::{system_path_to_file, VfsFile, VfsPath};
|
use ruff_db::system::{DbWithTestSystem, SystemPath};
|
||||||
|
|
||||||
use crate::db::tests::{create_resolver_builder, TestCase};
|
use crate::db::tests::{create_resolver_builder, TestCase};
|
||||||
use crate::module::ModuleKind;
|
use crate::module::ModuleKind;
|
||||||
|
@ -401,12 +401,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn first_party_module() -> anyhow::Result<()> {
|
fn first_party_module() -> anyhow::Result<()> {
|
||||||
let TestCase { db, src, .. } = setup_resolver_test();
|
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||||
|
|
||||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||||
let foo_path = src.join("foo.py");
|
let foo_path = src.join("foo.py");
|
||||||
db.memory_file_system()
|
db.write_file(&foo_path, "print('Hello, world!')")?;
|
||||||
.write_file(&foo_path, "print('Hello, world!')")?;
|
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
||||||
|
|
||||||
|
@ -422,7 +421,7 @@ mod tests {
|
||||||
assert_eq!(&foo_path, foo_module.file().path(&db));
|
assert_eq!(&foo_path, foo_module.file().path(&db));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
Some(foo_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo_path))
|
path_to_module(&db, &FilePath::System(foo_path))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -450,7 +449,7 @@ mod tests {
|
||||||
assert_eq!(ModuleKind::Module, functools_module.kind());
|
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||||
|
|
||||||
let expected_functools_path =
|
let expected_functools_path =
|
||||||
VfsPath::FileSystem(custom_typeshed.join("stdlib/functools.pyi"));
|
FilePath::System(custom_typeshed.join("stdlib/functools.pyi"));
|
||||||
assert_eq!(&expected_functools_path, functools_module.file().path(&db));
|
assert_eq!(&expected_functools_path, functools_module.file().path(&db));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -562,11 +561,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn first_party_precedence_over_stdlib() -> anyhow::Result<()> {
|
fn first_party_precedence_over_stdlib() -> anyhow::Result<()> {
|
||||||
let TestCase { db, src, .. } = setup_resolver_test();
|
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||||
|
|
||||||
let first_party_functools_path = src.join("functools.py");
|
let first_party_functools_path = src.join("functools.py");
|
||||||
db.memory_file_system()
|
db.write_file(&first_party_functools_path, "def update_wrapper(): ...")?;
|
||||||
.write_file(&first_party_functools_path, "def update_wrapper(): ...")?;
|
|
||||||
|
|
||||||
let functools_module_name = ModuleName::new_static("functools").unwrap();
|
let functools_module_name = ModuleName::new_static("functools").unwrap();
|
||||||
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
|
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
|
||||||
|
@ -584,7 +582,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(functools_module),
|
Some(functools_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(first_party_functools_path))
|
path_to_module(&db, &FilePath::System(first_party_functools_path))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -592,13 +590,12 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_package() -> anyhow::Result<()> {
|
fn resolve_package() -> anyhow::Result<()> {
|
||||||
let TestCase { src, db, .. } = setup_resolver_test();
|
let TestCase { src, mut db, .. } = setup_resolver_test();
|
||||||
|
|
||||||
let foo_dir = src.join("foo");
|
let foo_dir = src.join("foo");
|
||||||
let foo_path = foo_dir.join("__init__.py");
|
let foo_path = foo_dir.join("__init__.py");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file(&foo_path, "print('Hello, world!')")?;
|
||||||
.write_file(&foo_path, "print('Hello, world!')")?;
|
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||||
|
|
||||||
|
@ -608,28 +605,26 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(&foo_module),
|
Some(&foo_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo_path)).as_ref()
|
path_to_module(&db, &FilePath::System(foo_path)).as_ref()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolving by directory doesn't resolve to the init file.
|
// Resolving by directory doesn't resolve to the init file.
|
||||||
assert_eq!(None, path_to_module(&db, &VfsPath::FileSystem(foo_dir)));
|
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_dir)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn package_priority_over_module() -> anyhow::Result<()> {
|
fn package_priority_over_module() -> anyhow::Result<()> {
|
||||||
let TestCase { db, src, .. } = setup_resolver_test();
|
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||||
|
|
||||||
let foo_dir = src.join("foo");
|
let foo_dir = src.join("foo");
|
||||||
let foo_init = foo_dir.join("__init__.py");
|
let foo_init = foo_dir.join("__init__.py");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file(&foo_init, "print('Hello, world!')")?;
|
||||||
.write_file(&foo_init, "print('Hello, world!')")?;
|
|
||||||
|
|
||||||
let foo_py = src.join("foo.py");
|
let foo_py = src.join("foo.py");
|
||||||
db.memory_file_system()
|
db.write_file(&foo_py, "print('Hello, world!')")?;
|
||||||
.write_file(&foo_py, "print('Hello, world!')")?;
|
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||||
|
|
||||||
|
@ -639,45 +634,41 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
Some(foo_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo_init))
|
path_to_module(&db, &FilePath::System(foo_init))
|
||||||
);
|
);
|
||||||
assert_eq!(None, path_to_module(&db, &VfsPath::FileSystem(foo_py)));
|
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_py)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn typing_stub_over_module() -> anyhow::Result<()> {
|
fn typing_stub_over_module() -> anyhow::Result<()> {
|
||||||
let TestCase { db, src, .. } = setup_resolver_test();
|
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||||
|
|
||||||
let foo_stub = src.join("foo.pyi");
|
let foo_stub = src.join("foo.pyi");
|
||||||
let foo_py = src.join("foo.py");
|
let foo_py = src.join("foo.py");
|
||||||
db.memory_file_system()
|
db.write_files([(&foo_stub, "x: int"), (&foo_py, "print('Hello, world!')")])?;
|
||||||
.write_files([(&foo_stub, "x: int"), (&foo_py, "print('Hello, world!')")])?;
|
|
||||||
|
|
||||||
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||||
|
|
||||||
assert_eq!(&src, &foo.search_path());
|
assert_eq!(&src, &foo.search_path());
|
||||||
assert_eq!(&foo_stub, foo.file().path(&db));
|
assert_eq!(&foo_stub, foo.file().path(&db));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
||||||
Some(foo),
|
assert_eq!(None, path_to_module(&db, &FilePath::System(foo_py)));
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo_stub))
|
|
||||||
);
|
|
||||||
assert_eq!(None, path_to_module(&db, &VfsPath::FileSystem(foo_py)));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sub_packages() -> anyhow::Result<()> {
|
fn sub_packages() -> anyhow::Result<()> {
|
||||||
let TestCase { db, src, .. } = setup_resolver_test();
|
let TestCase { mut db, src, .. } = setup_resolver_test();
|
||||||
|
|
||||||
let foo = src.join("foo");
|
let foo = src.join("foo");
|
||||||
let bar = foo.join("bar");
|
let bar = foo.join("bar");
|
||||||
let baz = bar.join("baz.py");
|
let baz = bar.join("baz.py");
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
(&foo.join("__init__.py"), ""),
|
(&foo.join("__init__.py"), ""),
|
||||||
(&bar.join("__init__.py"), ""),
|
(&bar.join("__init__.py"), ""),
|
||||||
(&baz, "print('Hello, world!')"),
|
(&baz, "print('Hello, world!')"),
|
||||||
|
@ -691,7 +682,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(baz_module),
|
Some(baz_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(baz))
|
path_to_module(&db, &FilePath::System(baz))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -700,7 +691,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn namespace_package() -> anyhow::Result<()> {
|
fn namespace_package() -> anyhow::Result<()> {
|
||||||
let TestCase {
|
let TestCase {
|
||||||
db,
|
mut db,
|
||||||
src,
|
src,
|
||||||
site_packages,
|
site_packages,
|
||||||
..
|
..
|
||||||
|
@ -727,7 +718,7 @@ mod tests {
|
||||||
let child2 = parent2.join("child");
|
let child2 = parent2.join("child");
|
||||||
let two = child2.join("two.py");
|
let two = child2.join("two.py");
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
(&one, "print('Hello, world!')"),
|
(&one, "print('Hello, world!')"),
|
||||||
(&two, "print('Hello, world!')"),
|
(&two, "print('Hello, world!')"),
|
||||||
])?;
|
])?;
|
||||||
|
@ -737,14 +728,14 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(one_module),
|
Some(one_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(one))
|
path_to_module(&db, &FilePath::System(one))
|
||||||
);
|
);
|
||||||
|
|
||||||
let two_module =
|
let two_module =
|
||||||
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap()).unwrap();
|
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(two_module),
|
Some(two_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(two))
|
path_to_module(&db, &FilePath::System(two))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -753,7 +744,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn regular_package_in_namespace_package() -> anyhow::Result<()> {
|
fn regular_package_in_namespace_package() -> anyhow::Result<()> {
|
||||||
let TestCase {
|
let TestCase {
|
||||||
db,
|
mut db,
|
||||||
src,
|
src,
|
||||||
site_packages,
|
site_packages,
|
||||||
..
|
..
|
||||||
|
@ -780,7 +771,7 @@ mod tests {
|
||||||
let child2 = parent2.join("child");
|
let child2 = parent2.join("child");
|
||||||
let two = child2.join("two.py");
|
let two = child2.join("two.py");
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
(&child1.join("__init__.py"), "print('Hello, world!')"),
|
(&child1.join("__init__.py"), "print('Hello, world!')"),
|
||||||
(&one, "print('Hello, world!')"),
|
(&one, "print('Hello, world!')"),
|
||||||
(&two, "print('Hello, world!')"),
|
(&two, "print('Hello, world!')"),
|
||||||
|
@ -791,7 +782,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(one_module),
|
Some(one_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(one))
|
path_to_module(&db, &FilePath::System(one))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -804,7 +795,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_search_path_priority() -> anyhow::Result<()> {
|
fn module_search_path_priority() -> anyhow::Result<()> {
|
||||||
let TestCase {
|
let TestCase {
|
||||||
db,
|
mut db,
|
||||||
src,
|
src,
|
||||||
site_packages,
|
site_packages,
|
||||||
..
|
..
|
||||||
|
@ -813,8 +804,7 @@ mod tests {
|
||||||
let foo_src = src.join("foo.py");
|
let foo_src = src.join("foo.py");
|
||||||
let foo_site_packages = site_packages.join("foo.py");
|
let foo_site_packages = site_packages.join("foo.py");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_files([(&foo_src, ""), (&foo_site_packages, "")])?;
|
||||||
.write_files([(&foo_src, ""), (&foo_site_packages, "")])?;
|
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
|
||||||
|
|
||||||
|
@ -823,11 +813,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
Some(foo_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo_src))
|
path_to_module(&db, &FilePath::System(foo_src))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
None,
|
None,
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo_site_packages))
|
path_to_module(&db, &FilePath::System(foo_site_packages))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -843,10 +833,10 @@ mod tests {
|
||||||
custom_typeshed,
|
custom_typeshed,
|
||||||
} = setup_resolver_test();
|
} = setup_resolver_test();
|
||||||
|
|
||||||
db.with_os_file_system();
|
db.use_os_system();
|
||||||
|
|
||||||
let temp_dir = tempfile::tempdir()?;
|
let temp_dir = tempfile::tempdir()?;
|
||||||
let root = FileSystemPath::from_std_path(temp_dir.path()).unwrap();
|
let root = SystemPath::from_std_path(temp_dir.path()).unwrap();
|
||||||
|
|
||||||
let src = root.join(src);
|
let src = root.join(src);
|
||||||
let site_packages = root.join(site_packages);
|
let site_packages = root.join(site_packages);
|
||||||
|
@ -890,11 +880,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(foo_module),
|
Some(foo_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(foo))
|
path_to_module(&db, &FilePath::System(foo))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(bar_module),
|
Some(bar_module),
|
||||||
path_to_module(&db, &VfsPath::FileSystem(bar))
|
path_to_module(&db, &FilePath::System(bar))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -907,8 +897,7 @@ mod tests {
|
||||||
let foo_path = src.join("foo.py");
|
let foo_path = src.join("foo.py");
|
||||||
let bar_path = src.join("bar.py");
|
let bar_path = src.join("bar.py");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_files([(&foo_path, "x = 1"), (&bar_path, "y = 2")])?;
|
||||||
.write_files([(&foo_path, "x = 1"), (&bar_path, "y = 2")])?;
|
|
||||||
|
|
||||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||||
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
|
||||||
|
@ -946,8 +935,8 @@ mod tests {
|
||||||
assert_eq!(resolve_module(&db, foo_module_name.clone()), None);
|
assert_eq!(resolve_module(&db, foo_module_name.clone()), None);
|
||||||
|
|
||||||
// Now write the foo file
|
// Now write the foo file
|
||||||
db.memory_file_system().write_file(&foo_path, "x = 1")?;
|
db.write_file(&foo_path, "x = 1")?;
|
||||||
VfsFile::touch_path(&mut db, &VfsPath::FileSystem(foo_path.clone()));
|
|
||||||
let foo_file = system_path_to_file(&db, &foo_path).expect("foo.py to exist");
|
let foo_file = system_path_to_file(&db, &foo_path).expect("foo.py to exist");
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
|
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
|
||||||
|
@ -963,8 +952,7 @@ mod tests {
|
||||||
let foo_path = src.join("foo.py");
|
let foo_path = src.join("foo.py");
|
||||||
let foo_init_path = src.join("foo/__init__.py");
|
let foo_init_path = src.join("foo/__init__.py");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_files([(&foo_path, "x = 1"), (&foo_init_path, "x = 2")])?;
|
||||||
.write_files([(&foo_path, "x = 1"), (&foo_init_path, "x = 2")])?;
|
|
||||||
|
|
||||||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||||
let foo_module = resolve_module(&db, foo_module_name.clone()).expect("foo module to exist");
|
let foo_module = resolve_module(&db, foo_module_name.clone()).expect("foo module to exist");
|
||||||
|
@ -975,7 +963,7 @@ mod tests {
|
||||||
db.memory_file_system().remove_file(&foo_init_path)?;
|
db.memory_file_system().remove_file(&foo_init_path)?;
|
||||||
db.memory_file_system()
|
db.memory_file_system()
|
||||||
.remove_directory(foo_init_path.parent().unwrap())?;
|
.remove_directory(foo_init_path.parent().unwrap())?;
|
||||||
VfsFile::touch_path(&mut db, &VfsPath::FileSystem(foo_init_path));
|
File::touch_path(&mut db, &FilePath::System(foo_init_path));
|
||||||
|
|
||||||
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
|
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
|
||||||
assert_eq!(&foo_path, foo_module.file().path(&db));
|
assert_eq!(&foo_path, foo_module.file().path(&db));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use ruff_db::file_system::FileSystem;
|
use ruff_db::system::System;
|
||||||
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::supported_py_version::TargetVersion;
|
use crate::supported_py_version::TargetVersion;
|
||||||
|
@ -19,7 +19,7 @@ impl<'db> ResolverState<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn file_system(&self) -> &dyn FileSystem {
|
pub(crate) fn system(&self) -> &dyn System {
|
||||||
self.db.file_system()
|
self.db.system()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,33 @@
|
||||||
mod versions;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
pub(crate) use versions::{
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
|
|
||||||
|
pub(crate) use self::versions::{
|
||||||
parse_typeshed_versions, LazyTypeshedVersions, TypeshedVersionsQueryResult,
|
parse_typeshed_versions, LazyTypeshedVersions, TypeshedVersionsQueryResult,
|
||||||
};
|
};
|
||||||
pub use versions::{TypeshedVersionsParseError, TypeshedVersionsParseErrorKind};
|
pub use self::versions::{TypeshedVersionsParseError, TypeshedVersionsParseErrorKind};
|
||||||
|
|
||||||
|
mod versions;
|
||||||
|
|
||||||
|
// The file path here is hardcoded in this crate's `build.rs` script.
|
||||||
|
// Luckily this crate will fail to build if this file isn't available at build time.
|
||||||
|
static TYPESHED_ZIP_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/zipped_typeshed.zip"));
|
||||||
|
|
||||||
|
pub fn vendored_typeshed_stubs() -> &'static VendoredFileSystem {
|
||||||
|
static VENDORED_TYPESHED_STUBS: Lazy<VendoredFileSystem> =
|
||||||
|
Lazy::new(|| VendoredFileSystem::new_static(TYPESHED_ZIP_BYTES).unwrap());
|
||||||
|
&VENDORED_TYPESHED_STUBS
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use ruff_db::vendored::VendoredFileSystem;
|
use ruff_db::vendored::VendoredPath;
|
||||||
use ruff_db::vfs::VendoredPath;
|
|
||||||
|
|
||||||
// The file path here is hardcoded in this crate's `build.rs` script.
|
use crate::typeshed::TYPESHED_ZIP_BYTES;
|
||||||
// Luckily this crate will fail to build if this file isn't available at build time.
|
use crate::vendored_typeshed_stubs;
|
||||||
const TYPESHED_ZIP_BYTES: &[u8] =
|
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/zipped_typeshed.zip"));
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn typeshed_zip_created_at_build_time() {
|
fn typeshed_zip_created_at_build_time() {
|
||||||
|
@ -39,7 +50,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn typeshed_vfs_consistent_with_vendored_stubs() {
|
fn typeshed_vfs_consistent_with_vendored_stubs() {
|
||||||
let vendored_typeshed_dir = Path::new("vendor/typeshed").canonicalize().unwrap();
|
let vendored_typeshed_dir = Path::new("vendor/typeshed").canonicalize().unwrap();
|
||||||
let vendored_typeshed_stubs = VendoredFileSystem::new(TYPESHED_ZIP_BYTES).unwrap();
|
let vendored_typeshed_stubs = vendored_typeshed_stubs();
|
||||||
|
|
||||||
let mut empty_iterator = true;
|
let mut empty_iterator = true;
|
||||||
for entry in walkdir::WalkDir::new(&vendored_typeshed_dir).min_depth(1) {
|
for entry in walkdir::WalkDir::new(&vendored_typeshed_dir).min_depth(1) {
|
||||||
|
@ -69,7 +80,7 @@ mod tests {
|
||||||
|
|
||||||
let vendored_path_kind = vendored_typeshed_stubs
|
let vendored_path_kind = vendored_typeshed_stubs
|
||||||
.metadata(vendored_path)
|
.metadata(vendored_path)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|_| {
|
||||||
panic!(
|
panic!(
|
||||||
"Expected metadata for {vendored_path:?} to be retrievable from the `VendoredFileSystem!
|
"Expected metadata for {vendored_path:?} to be retrievable from the `VendoredFileSystem!
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ use std::num::{NonZeroU16, NonZeroUsize};
|
||||||
use std::ops::{RangeFrom, RangeInclusive};
|
use std::ops::{RangeFrom, RangeInclusive};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ruff_db::file_system::FileSystemPath;
|
|
||||||
use ruff_db::source::source_text;
|
|
||||||
use ruff_db::vfs::{system_path_to_file, VfsFile};
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
|
use ruff_db::source::source_text;
|
||||||
|
use ruff_db::system::SystemPath;
|
||||||
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::supported_py_version::TargetVersion;
|
use crate::supported_py_version::TargetVersion;
|
||||||
|
@ -40,7 +41,7 @@ impl<'db> LazyTypeshedVersions<'db> {
|
||||||
&self,
|
&self,
|
||||||
module: &ModuleName,
|
module: &ModuleName,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
stdlib_root: &FileSystemPath,
|
stdlib_root: &SystemPath,
|
||||||
target_version: TargetVersion,
|
target_version: TargetVersion,
|
||||||
) -> TypeshedVersionsQueryResult {
|
) -> TypeshedVersionsQueryResult {
|
||||||
let versions = self.0.get_or_init(|| {
|
let versions = self.0.get_or_init(|| {
|
||||||
|
@ -64,7 +65,7 @@ impl<'db> LazyTypeshedVersions<'db> {
|
||||||
#[salsa::tracked(return_ref)]
|
#[salsa::tracked(return_ref)]
|
||||||
pub(crate) fn parse_typeshed_versions(
|
pub(crate) fn parse_typeshed_versions(
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
versions_file: VfsFile,
|
versions_file: File,
|
||||||
) -> Result<TypeshedVersions, TypeshedVersionsParseError> {
|
) -> Result<TypeshedVersions, TypeshedVersionsParseError> {
|
||||||
let file_content = source_text(db.upcast(), versions_file);
|
let file_content = source_text(db.upcast(), versions_file);
|
||||||
file_content.parse()
|
file_content.parse()
|
||||||
|
@ -429,10 +430,10 @@ mod tests {
|
||||||
use std::num::{IntErrorKind, NonZeroU16};
|
use std::num::{IntErrorKind, NonZeroU16};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
const TYPESHED_STDLIB_DIR: &str = "stdlib";
|
const TYPESHED_STDLIB_DIR: &str = "stdlib";
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use salsa::DbWithJar;
|
use salsa::DbWithJar;
|
||||||
|
|
||||||
use ruff_db::{Db as SourceDb, Upcast};
|
|
||||||
|
|
||||||
use red_knot_module_resolver::Db as ResolverDb;
|
use red_knot_module_resolver::Db as ResolverDb;
|
||||||
|
use ruff_db::{Db as SourceDb, Upcast};
|
||||||
|
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::symbol::{public_symbols_map, PublicSymbolId, ScopeId};
|
use crate::semantic_index::symbol::{public_symbols_map, PublicSymbolId, ScopeId};
|
||||||
|
@ -45,9 +44,10 @@ pub(crate) mod tests {
|
||||||
use salsa::storage::HasIngredientsFor;
|
use salsa::storage::HasIngredientsFor;
|
||||||
use salsa::DebugWithDb;
|
use salsa::DebugWithDb;
|
||||||
|
|
||||||
use red_knot_module_resolver::{Db as ResolverDb, Jar as ResolverJar};
|
use red_knot_module_resolver::{vendored_typeshed_stubs, Db as ResolverDb, Jar as ResolverJar};
|
||||||
use ruff_db::file_system::{FileSystem, MemoryFileSystem, OsFileSystem};
|
use ruff_db::files::Files;
|
||||||
use ruff_db::vfs::Vfs;
|
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
|
||||||
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast};
|
use ruff_db::{Db as SourceDb, Jar as SourceJar, Upcast};
|
||||||
|
|
||||||
use super::{Db, Jar};
|
use super::{Db, Jar};
|
||||||
|
@ -55,8 +55,9 @@ pub(crate) mod tests {
|
||||||
#[salsa::db(Jar, ResolverJar, SourceJar)]
|
#[salsa::db(Jar, ResolverJar, SourceJar)]
|
||||||
pub(crate) struct TestDb {
|
pub(crate) struct TestDb {
|
||||||
storage: salsa::Storage<Self>,
|
storage: salsa::Storage<Self>,
|
||||||
vfs: Vfs,
|
files: Files,
|
||||||
file_system: TestFileSystem,
|
system: TestSystem,
|
||||||
|
vendored: VendoredFileSystem,
|
||||||
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,29 +65,13 @@ pub(crate) mod tests {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
storage: salsa::Storage::default(),
|
storage: salsa::Storage::default(),
|
||||||
file_system: TestFileSystem::Memory(MemoryFileSystem::default()),
|
system: TestSystem::default(),
|
||||||
|
vendored: vendored_typeshed_stubs().snapshot(),
|
||||||
events: std::sync::Arc::default(),
|
events: std::sync::Arc::default(),
|
||||||
vfs: Vfs::with_stubbed_vendored(),
|
files: Files::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the memory file system.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
/// If this test db isn't using a memory file system.
|
|
||||||
pub(crate) fn memory_file_system(&self) -> &MemoryFileSystem {
|
|
||||||
if let TestFileSystem::Memory(fs) = &self.file_system {
|
|
||||||
fs
|
|
||||||
} else {
|
|
||||||
panic!("The test db is not using a memory file system");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn vfs_mut(&mut self) -> &mut Vfs {
|
|
||||||
&mut self.vfs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes the salsa events.
|
/// Takes the salsa events.
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
|
@ -107,16 +92,27 @@ pub(crate) mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceDb for TestDb {
|
impl DbWithTestSystem for TestDb {
|
||||||
fn file_system(&self) -> &dyn FileSystem {
|
fn test_system(&self) -> &TestSystem {
|
||||||
match &self.file_system {
|
&self.system
|
||||||
TestFileSystem::Memory(fs) => fs,
|
}
|
||||||
TestFileSystem::Os(fs) => fs,
|
|
||||||
|
fn test_system_mut(&mut self) -> &mut TestSystem {
|
||||||
|
&mut self.system
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vfs(&self) -> &Vfs {
|
impl SourceDb for TestDb {
|
||||||
&self.vfs
|
fn vendored(&self) -> &VendoredFileSystem {
|
||||||
|
&self.vendored
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system(&self) -> &dyn System {
|
||||||
|
&self.system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn files(&self) -> &Files {
|
||||||
|
&self.files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,22 +143,14 @@ pub(crate) mod tests {
|
||||||
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
||||||
salsa::Snapshot::new(Self {
|
salsa::Snapshot::new(Self {
|
||||||
storage: self.storage.snapshot(),
|
storage: self.storage.snapshot(),
|
||||||
vfs: self.vfs.snapshot(),
|
files: self.files.snapshot(),
|
||||||
file_system: match &self.file_system {
|
system: self.system.snapshot(),
|
||||||
TestFileSystem::Memory(memory) => TestFileSystem::Memory(memory.snapshot()),
|
vendored: self.vendored.snapshot(),
|
||||||
TestFileSystem::Os(fs) => TestFileSystem::Os(fs.snapshot()),
|
|
||||||
},
|
|
||||||
events: self.events.clone(),
|
events: self.events.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TestFileSystem {
|
|
||||||
Memory(MemoryFileSystem),
|
|
||||||
#[allow(dead_code)]
|
|
||||||
Os(OsFileSystem),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn assert_will_run_function_query<'db, C, Db, Jar>(
|
pub(crate) fn assert_will_run_function_query<'db, C, Db, Jar>(
|
||||||
db: &'db Db,
|
db: &'db Db,
|
||||||
to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient<C>,
|
to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient<C>,
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::vfs::VfsFile;
|
|
||||||
use ruff_index::{IndexSlice, IndexVec};
|
use ruff_index::{IndexSlice, IndexVec};
|
||||||
|
|
||||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||||
|
@ -28,7 +28,7 @@ type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), ()>;
|
||||||
///
|
///
|
||||||
/// Prefer using [`symbol_table`] when working with symbols from a single scope.
|
/// Prefer using [`symbol_table`] when working with symbols from a single scope.
|
||||||
#[salsa::tracked(return_ref, no_eq)]
|
#[salsa::tracked(return_ref, no_eq)]
|
||||||
pub(crate) fn semantic_index(db: &dyn Db, file: VfsFile) -> SemanticIndex<'_> {
|
pub(crate) fn semantic_index(db: &dyn Db, file: File) -> SemanticIndex<'_> {
|
||||||
let _span = tracing::trace_span!("semantic_index", ?file).entered();
|
let _span = tracing::trace_span!("semantic_index", ?file).entered();
|
||||||
|
|
||||||
let parsed = parsed_module(db.upcast(), file);
|
let parsed = parsed_module(db.upcast(), file);
|
||||||
|
@ -51,7 +51,7 @@ pub(crate) fn symbol_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<Sym
|
||||||
|
|
||||||
/// Returns the root scope of `file`.
|
/// Returns the root scope of `file`.
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub(crate) fn root_scope(db: &dyn Db, file: VfsFile) -> ScopeId<'_> {
|
pub(crate) fn root_scope(db: &dyn Db, file: File) -> ScopeId<'_> {
|
||||||
let _span = tracing::trace_span!("root_scope", ?file).entered();
|
let _span = tracing::trace_span!("root_scope", ?file).entered();
|
||||||
|
|
||||||
FileScopeId::root().to_scope_id(db, file)
|
FileScopeId::root().to_scope_id(db, file)
|
||||||
|
@ -61,7 +61,7 @@ pub(crate) fn root_scope(db: &dyn Db, file: VfsFile) -> ScopeId<'_> {
|
||||||
/// no symbol with the given name exists.
|
/// no symbol with the given name exists.
|
||||||
pub(crate) fn public_symbol<'db>(
|
pub(crate) fn public_symbol<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Option<PublicSymbolId<'db>> {
|
) -> Option<PublicSymbolId<'db>> {
|
||||||
let root_scope = root_scope(db, file);
|
let root_scope = root_scope(db, file);
|
||||||
|
@ -272,8 +272,9 @@ impl FusedIterator for ChildrenIter<'_> {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::vfs::{system_path_to_file, VfsFile};
|
use ruff_db::system::DbWithTestSystem;
|
||||||
|
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::semantic_index::symbol::{FileScopeId, Scope, ScopeKind, SymbolTable};
|
use crate::semantic_index::symbol::{FileScopeId, Scope, ScopeKind, SymbolTable};
|
||||||
|
@ -282,14 +283,12 @@ mod tests {
|
||||||
|
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
db: TestDb,
|
db: TestDb,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_case(content: impl ToString) -> TestCase {
|
fn test_case(content: impl ToString) -> TestCase {
|
||||||
let db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
db.memory_file_system()
|
db.write_file("test.py", content).unwrap();
|
||||||
.write_file("test.py", content)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, "test.py").unwrap();
|
let file = system_path_to_file(&db, "test.py").unwrap();
|
||||||
|
|
||||||
|
@ -631,7 +630,7 @@ class C[T]:
|
||||||
fn scope_names<'a>(
|
fn scope_names<'a>(
|
||||||
scopes: impl Iterator<Item = (FileScopeId, &'a Scope)>,
|
scopes: impl Iterator<Item = (FileScopeId, &'a Scope)>,
|
||||||
db: &'a dyn Db,
|
db: &'a dyn Db,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
) -> Vec<&'a str> {
|
) -> Vec<&'a str> {
|
||||||
scopes
|
scopes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::ParsedModule;
|
use ruff_db::parsed::ParsedModule;
|
||||||
use ruff_db::vfs::VfsFile;
|
|
||||||
use ruff_index::IndexVec;
|
use ruff_index::IndexVec;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
|
@ -22,7 +22,7 @@ use crate::Db;
|
||||||
pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||||
// Builder state
|
// Builder state
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
module: &'db ParsedModule,
|
module: &'db ParsedModule,
|
||||||
scope_stack: Vec<FileScopeId>,
|
scope_stack: Vec<FileScopeId>,
|
||||||
/// the target we're currently inferring
|
/// the target we're currently inferring
|
||||||
|
@ -42,7 +42,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast>
|
||||||
where
|
where
|
||||||
'db: 'ast,
|
'db: 'ast,
|
||||||
{
|
{
|
||||||
pub(super) fn new(db: &'db dyn Db, file: VfsFile, parsed: &'db ParsedModule) -> Self {
|
pub(super) fn new(db: &'db dyn Db, file: File, parsed: &'db ParsedModule) -> Self {
|
||||||
let mut builder = Self {
|
let mut builder = Self {
|
||||||
db,
|
db,
|
||||||
file,
|
file,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::ParsedModule;
|
use ruff_db::parsed::ParsedModule;
|
||||||
use ruff_db::vfs::VfsFile;
|
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
|
||||||
use crate::ast_node_ref::AstNodeRef;
|
use crate::ast_node_ref::AstNodeRef;
|
||||||
|
@ -10,7 +10,7 @@ use crate::semantic_index::symbol::{FileScopeId, ScopedSymbolId};
|
||||||
pub struct Definition<'db> {
|
pub struct Definition<'db> {
|
||||||
/// The file in which the definition is defined.
|
/// The file in which the definition is defined.
|
||||||
#[id]
|
#[id]
|
||||||
pub(super) file: VfsFile,
|
pub(super) file: File,
|
||||||
|
|
||||||
/// The scope in which the definition is defined.
|
/// The scope in which the definition is defined.
|
||||||
#[id]
|
#[id]
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::ops::Range;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use hashbrown::hash_map::RawEntryMut;
|
use hashbrown::hash_map::RawEntryMut;
|
||||||
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::ParsedModule;
|
use ruff_db::parsed::ParsedModule;
|
||||||
use ruff_db::vfs::VfsFile;
|
|
||||||
use ruff_index::{newtype_index, IndexVec};
|
use ruff_index::{newtype_index, IndexVec};
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
use ruff_python_ast::{self as ast};
|
use ruff_python_ast::{self as ast};
|
||||||
|
@ -79,7 +79,7 @@ bitflags! {
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub struct PublicSymbolId<'db> {
|
pub struct PublicSymbolId<'db> {
|
||||||
#[id]
|
#[id]
|
||||||
pub(crate) file: VfsFile,
|
pub(crate) file: File,
|
||||||
#[id]
|
#[id]
|
||||||
pub(crate) scoped_symbol_id: ScopedSymbolId,
|
pub(crate) scoped_symbol_id: ScopedSymbolId,
|
||||||
}
|
}
|
||||||
|
@ -116,14 +116,14 @@ impl ScopedSymbolId {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// May panic if the symbol does not belong to `file` or is not a symbol of `file`'s root scope.
|
/// May panic if the symbol does not belong to `file` or is not a symbol of `file`'s root scope.
|
||||||
pub(crate) fn to_public_symbol(self, db: &dyn Db, file: VfsFile) -> PublicSymbolId {
|
pub(crate) fn to_public_symbol(self, db: &dyn Db, file: File) -> PublicSymbolId {
|
||||||
let symbols = public_symbols_map(db, file);
|
let symbols = public_symbols_map(db, file);
|
||||||
symbols.public(self)
|
symbols.public(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[salsa::tracked(return_ref)]
|
#[salsa::tracked(return_ref)]
|
||||||
pub(crate) fn public_symbols_map(db: &dyn Db, file: VfsFile) -> PublicSymbolsMap<'_> {
|
pub(crate) fn public_symbols_map(db: &dyn Db, file: File) -> PublicSymbolsMap<'_> {
|
||||||
let _span = tracing::trace_span!("public_symbols_map", ?file).entered();
|
let _span = tracing::trace_span!("public_symbols_map", ?file).entered();
|
||||||
|
|
||||||
let module_scope = root_scope(db, file);
|
let module_scope = root_scope(db, file);
|
||||||
|
@ -156,7 +156,7 @@ impl<'db> PublicSymbolsMap<'db> {
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub struct ScopeId<'db> {
|
pub struct ScopeId<'db> {
|
||||||
#[id]
|
#[id]
|
||||||
pub file: VfsFile,
|
pub file: File,
|
||||||
#[id]
|
#[id]
|
||||||
pub file_scope_id: FileScopeId,
|
pub file_scope_id: FileScopeId,
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ impl FileScopeId {
|
||||||
FileScopeId::from_u32(0)
|
FileScopeId::from_u32(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_scope_id(self, db: &dyn Db, file: VfsFile) -> ScopeId<'_> {
|
pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> {
|
||||||
let index = semantic_index(db, file);
|
let index = semantic_index(db, file);
|
||||||
index.scope_ids_by_scope[self]
|
index.scope_ids_by_scope[self]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use red_knot_module_resolver::{resolve_module, Module, ModuleName};
|
use red_knot_module_resolver::{resolve_module, Module, ModuleName};
|
||||||
use ruff_db::vfs::VfsFile;
|
use ruff_db::files::File;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef};
|
use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef};
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ use crate::Db;
|
||||||
|
|
||||||
pub struct SemanticModel<'db> {
|
pub struct SemanticModel<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> SemanticModel<'db> {
|
impl<'db> SemanticModel<'db> {
|
||||||
pub fn new(db: &'db dyn Db, file: VfsFile) -> Self {
|
pub fn new(db: &'db dyn Db, file: File) -> Self {
|
||||||
Self { db, file }
|
Self { db, file }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +182,9 @@ mod tests {
|
||||||
use red_knot_module_resolver::{
|
use red_knot_module_resolver::{
|
||||||
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
||||||
};
|
};
|
||||||
use ruff_db::file_system::FileSystemPathBuf;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::vfs::system_path_to_file;
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
|
@ -196,7 +196,7 @@ mod tests {
|
||||||
&mut db,
|
&mut db,
|
||||||
RawModuleResolutionSettings {
|
RawModuleResolutionSettings {
|
||||||
extra_paths: vec![],
|
extra_paths: vec![],
|
||||||
workspace_root: FileSystemPathBuf::from("/src"),
|
workspace_root: SystemPathBuf::from("/src"),
|
||||||
site_packages: None,
|
site_packages: None,
|
||||||
custom_typeshed: None,
|
custom_typeshed: None,
|
||||||
target_version: TargetVersion::Py38,
|
target_version: TargetVersion::Py38,
|
||||||
|
@ -208,10 +208,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_ty() -> anyhow::Result<()> {
|
fn function_ty() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("/src/foo.py", "def test(): pass")?;
|
||||||
.write_file("/src/foo.py", "def test(): pass")?;
|
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||||
|
|
||||||
let ast = parsed_module(&db, foo);
|
let ast = parsed_module(&db, foo);
|
||||||
|
@ -227,10 +226,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn class_ty() -> anyhow::Result<()> {
|
fn class_ty() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("/src/foo.py", "class Test: pass")?;
|
||||||
.write_file("/src/foo.py", "class Test: pass")?;
|
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||||
|
|
||||||
let ast = parsed_module(&db, foo);
|
let ast = parsed_module(&db, foo);
|
||||||
|
@ -246,9 +244,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alias_ty() -> anyhow::Result<()> {
|
fn alias_ty() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
("/src/foo.py", "class Test: pass"),
|
("/src/foo.py", "class Test: pass"),
|
||||||
("/src/bar.py", "from foo import Test"),
|
("/src/bar.py", "from foo import Test"),
|
||||||
])?;
|
])?;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::vfs::VfsFile;
|
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
|
|
||||||
use crate::semantic_index::symbol::{NodeWithScopeKind, PublicSymbolId, ScopeId};
|
use crate::semantic_index::symbol::{NodeWithScopeKind, PublicSymbolId, ScopeId};
|
||||||
|
@ -49,7 +49,7 @@ pub(crate) fn public_symbol_ty<'db>(db: &'db dyn Db, symbol: PublicSymbolId<'db>
|
||||||
/// Shorthand for `public_symbol_ty` that takes a symbol name instead of a [`PublicSymbolId`].
|
/// Shorthand for `public_symbol_ty` that takes a symbol name instead of a [`PublicSymbolId`].
|
||||||
pub(crate) fn public_symbol_ty_by_name<'db>(
|
pub(crate) fn public_symbol_ty_by_name<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
file: VfsFile,
|
file: File,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Option<Type<'db>> {
|
) -> Option<Type<'db>> {
|
||||||
let symbol = public_symbol(db, file, name)?;
|
let symbol = public_symbol(db, file, name)?;
|
||||||
|
@ -105,7 +105,7 @@ pub enum Type<'db> {
|
||||||
/// a specific function object
|
/// a specific function object
|
||||||
Function(FunctionType<'db>),
|
Function(FunctionType<'db>),
|
||||||
/// a specific module object
|
/// a specific module object
|
||||||
Module(VfsFile),
|
Module(File),
|
||||||
/// a specific class object
|
/// a specific class object
|
||||||
Class(ClassType<'db>),
|
Class(ClassType<'db>),
|
||||||
/// the set of Python objects with the given class in their __class__'s method resolution order
|
/// the set of Python objects with the given class in their __class__'s method resolution order
|
||||||
|
@ -274,9 +274,9 @@ mod tests {
|
||||||
use red_knot_module_resolver::{
|
use red_knot_module_resolver::{
|
||||||
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
||||||
};
|
};
|
||||||
use ruff_db::file_system::FileSystemPathBuf;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::vfs::system_path_to_file;
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
|
||||||
use crate::db::tests::{
|
use crate::db::tests::{
|
||||||
assert_will_not_run_function_query, assert_will_run_function_query, TestDb,
|
assert_will_not_run_function_query, assert_will_run_function_query, TestDb,
|
||||||
|
@ -292,7 +292,7 @@ mod tests {
|
||||||
RawModuleResolutionSettings {
|
RawModuleResolutionSettings {
|
||||||
target_version: TargetVersion::Py38,
|
target_version: TargetVersion::Py38,
|
||||||
extra_paths: vec![],
|
extra_paths: vec![],
|
||||||
workspace_root: FileSystemPathBuf::from("/src"),
|
workspace_root: SystemPathBuf::from("/src"),
|
||||||
site_packages: None,
|
site_packages: None,
|
||||||
custom_typeshed: None,
|
custom_typeshed: None,
|
||||||
},
|
},
|
||||||
|
@ -303,9 +303,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn local_inference() -> anyhow::Result<()> {
|
fn local_inference() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file("/src/a.py", "x = 10")?;
|
db.write_file("/src/a.py", "x = 10")?;
|
||||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||||
|
|
||||||
let parsed = parsed_module(&db, a);
|
let parsed = parsed_module(&db, a);
|
||||||
|
@ -324,7 +324,7 @@ mod tests {
|
||||||
fn dependency_public_symbol_type_change() -> anyhow::Result<()> {
|
fn dependency_public_symbol_type_change() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
("/src/a.py", "from foo import x"),
|
("/src/a.py", "from foo import x"),
|
||||||
("/src/foo.py", "x = 10\ndef foo(): ..."),
|
("/src/foo.py", "x = 10\ndef foo(): ..."),
|
||||||
])?;
|
])?;
|
||||||
|
@ -335,11 +335,7 @@ mod tests {
|
||||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
||||||
|
|
||||||
// Change `x` to a different value
|
// Change `x` to a different value
|
||||||
db.memory_file_system()
|
db.write_file("/src/foo.py", "x = 20\ndef foo(): ...")?;
|
||||||
.write_file("/src/foo.py", "x = 20\ndef foo(): ...")?;
|
|
||||||
|
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
|
||||||
foo.touch(&mut db);
|
|
||||||
|
|
||||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||||
|
|
||||||
|
@ -365,7 +361,7 @@ mod tests {
|
||||||
fn dependency_non_public_symbol_change() -> anyhow::Result<()> {
|
fn dependency_non_public_symbol_change() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
("/src/a.py", "from foo import x"),
|
("/src/a.py", "from foo import x"),
|
||||||
("/src/foo.py", "x = 10\ndef foo(): y = 1"),
|
("/src/foo.py", "x = 10\ndef foo(): y = 1"),
|
||||||
])?;
|
])?;
|
||||||
|
@ -375,13 +371,9 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("/src/foo.py", "x = 10\ndef foo(): pass")?;
|
||||||
.write_file("/src/foo.py", "x = 10\ndef foo(): pass")?;
|
|
||||||
|
|
||||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
|
||||||
|
|
||||||
foo.touch(&mut db);
|
|
||||||
|
|
||||||
db.clear_salsa_events();
|
db.clear_salsa_events();
|
||||||
|
|
||||||
|
@ -407,7 +399,7 @@ mod tests {
|
||||||
fn dependency_unrelated_public_symbol() -> anyhow::Result<()> {
|
fn dependency_unrelated_public_symbol() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
("/src/a.py", "from foo import x"),
|
("/src/a.py", "from foo import x"),
|
||||||
("/src/foo.py", "x = 10\ny = 20"),
|
("/src/foo.py", "x = 10\ny = 20"),
|
||||||
])?;
|
])?;
|
||||||
|
@ -417,13 +409,9 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("/src/foo.py", "x = 10\ny = 30")?;
|
||||||
.write_file("/src/foo.py", "x = 10\ny = 30")?;
|
|
||||||
|
|
||||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
|
||||||
|
|
||||||
foo.touch(&mut db);
|
|
||||||
|
|
||||||
db.clear_salsa_events();
|
db.clear_salsa_events();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::borrow::Cow;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use red_knot_module_resolver::{resolve_module, ModuleName};
|
use red_knot_module_resolver::{resolve_module, ModuleName};
|
||||||
use ruff_db::vfs::VfsFile;
|
use ruff_db::files::File;
|
||||||
use ruff_index::IndexVec;
|
use ruff_index::IndexVec;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::{ExprContext, TypeParams};
|
use ruff_python_ast::{ExprContext, TypeParams};
|
||||||
|
@ -58,7 +58,7 @@ pub(super) struct TypeInferenceBuilder<'db> {
|
||||||
// Cached lookups
|
// Cached lookups
|
||||||
index: &'db SemanticIndex<'db>,
|
index: &'db SemanticIndex<'db>,
|
||||||
file_scope_id: FileScopeId,
|
file_scope_id: FileScopeId,
|
||||||
file_id: VfsFile,
|
file_id: File,
|
||||||
symbol_table: Arc<SymbolTable<'db>>,
|
symbol_table: Arc<SymbolTable<'db>>,
|
||||||
|
|
||||||
/// The type inference results
|
/// The type inference results
|
||||||
|
@ -601,8 +601,8 @@ mod tests {
|
||||||
use red_knot_module_resolver::{
|
use red_knot_module_resolver::{
|
||||||
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
set_module_resolution_settings, RawModuleResolutionSettings, TargetVersion,
|
||||||
};
|
};
|
||||||
use ruff_db::file_system::FileSystemPathBuf;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::vfs::system_path_to_file;
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
|
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
|
@ -616,7 +616,7 @@ mod tests {
|
||||||
RawModuleResolutionSettings {
|
RawModuleResolutionSettings {
|
||||||
target_version: TargetVersion::Py38,
|
target_version: TargetVersion::Py38,
|
||||||
extra_paths: Vec::new(),
|
extra_paths: Vec::new(),
|
||||||
workspace_root: FileSystemPathBuf::from("/src"),
|
workspace_root: SystemPathBuf::from("/src"),
|
||||||
site_packages: None,
|
site_packages: None,
|
||||||
custom_typeshed: None,
|
custom_typeshed: None,
|
||||||
},
|
},
|
||||||
|
@ -634,9 +634,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn follow_import_to_class() -> anyhow::Result<()> {
|
fn follow_import_to_class() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
("src/a.py", "from b import C as D; E = D"),
|
("src/a.py", "from b import C as D; E = D"),
|
||||||
("src/b.py", "class C: pass"),
|
("src/b.py", "class C: pass"),
|
||||||
])?;
|
])?;
|
||||||
|
@ -648,9 +648,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_base_class_by_name() -> anyhow::Result<()> {
|
fn resolve_base_class_by_name() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file(
|
db.write_file(
|
||||||
"src/mod.py",
|
"src/mod.py",
|
||||||
r#"
|
r#"
|
||||||
class Base:
|
class Base:
|
||||||
|
@ -680,9 +680,9 @@ class Sub(Base):
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_method() -> anyhow::Result<()> {
|
fn resolve_method() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file(
|
db.write_file(
|
||||||
"src/mod.py",
|
"src/mod.py",
|
||||||
"
|
"
|
||||||
class C:
|
class C:
|
||||||
|
@ -710,9 +710,9 @@ class C:
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_module_member() -> anyhow::Result<()> {
|
fn resolve_module_member() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_files([
|
db.write_files([
|
||||||
("src/a.py", "import b; D = b.C"),
|
("src/a.py", "import b; D = b.C"),
|
||||||
("src/b.py", "class C: pass"),
|
("src/b.py", "class C: pass"),
|
||||||
])?;
|
])?;
|
||||||
|
@ -724,9 +724,9 @@ class C:
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_literal() -> anyhow::Result<()> {
|
fn resolve_literal() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file("src/a.py", "x = 1")?;
|
db.write_file("src/a.py", "x = 1")?;
|
||||||
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "Literal[1]");
|
assert_public_ty(&db, "src/a.py", "x", "Literal[1]");
|
||||||
|
|
||||||
|
@ -735,9 +735,9 @@ class C:
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_union() -> anyhow::Result<()> {
|
fn resolve_union() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file(
|
db.write_file(
|
||||||
"src/a.py",
|
"src/a.py",
|
||||||
"
|
"
|
||||||
if flag:
|
if flag:
|
||||||
|
@ -754,9 +754,9 @@ else:
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn literal_int_arithmetic() -> anyhow::Result<()> {
|
fn literal_int_arithmetic() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file(
|
db.write_file(
|
||||||
"src/a.py",
|
"src/a.py",
|
||||||
"
|
"
|
||||||
a = 2 + 1
|
a = 2 + 1
|
||||||
|
@ -778,10 +778,9 @@ e = 5 % 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walrus() -> anyhow::Result<()> {
|
fn walrus() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("src/a.py", "x = (y := 1) + 1")?;
|
||||||
.write_file("src/a.py", "x = (y := 1) + 1")?;
|
|
||||||
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "Literal[2]");
|
assert_public_ty(&db, "src/a.py", "x", "Literal[2]");
|
||||||
assert_public_ty(&db, "src/a.py", "y", "Literal[1]");
|
assert_public_ty(&db, "src/a.py", "y", "Literal[1]");
|
||||||
|
@ -791,10 +790,9 @@ e = 5 % 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ifexpr() -> anyhow::Result<()> {
|
fn ifexpr() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("src/a.py", "x = 1 if flag else 2")?;
|
||||||
.write_file("src/a.py", "x = 1 if flag else 2")?;
|
|
||||||
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "Literal[1, 2]");
|
assert_public_ty(&db, "src/a.py", "x", "Literal[1, 2]");
|
||||||
|
|
||||||
|
@ -803,9 +801,9 @@ e = 5 % 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ifexpr_walrus() -> anyhow::Result<()> {
|
fn ifexpr_walrus() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system().write_file(
|
db.write_file(
|
||||||
"src/a.py",
|
"src/a.py",
|
||||||
"
|
"
|
||||||
y = z = 0
|
y = z = 0
|
||||||
|
@ -824,10 +822,9 @@ b = z
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ifexpr_nested() -> anyhow::Result<()> {
|
fn ifexpr_nested() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("src/a.py", "x = 1 if flag else 2 if flag2 else 3")?;
|
||||||
.write_file("src/a.py", "x = 1 if flag else 2 if flag2 else 3")?;
|
|
||||||
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "Literal[1, 2, 3]");
|
assert_public_ty(&db, "src/a.py", "x", "Literal[1, 2, 3]");
|
||||||
|
|
||||||
|
@ -836,10 +833,9 @@ b = z
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn none() -> anyhow::Result<()> {
|
fn none() -> anyhow::Result<()> {
|
||||||
let db = setup_db();
|
let mut db = setup_db();
|
||||||
|
|
||||||
db.memory_file_system()
|
db.write_file("src/a.py", "x = 1 if flag else None")?;
|
||||||
.write_file("src/a.py", "x = 1 if flag else None")?;
|
|
||||||
|
|
||||||
assert_public_ty(&db, "src/a.py", "x", "Literal[1] | None");
|
assert_public_ty(&db, "src/a.py", "x", "Literal[1] | None");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,9 +8,9 @@ use red_knot_module_resolver::{
|
||||||
use ruff_benchmark::criterion::{
|
use ruff_benchmark::criterion::{
|
||||||
criterion_group, criterion_main, BatchSize, Criterion, Throughput,
|
criterion_group, criterion_main, BatchSize, Criterion, Throughput,
|
||||||
};
|
};
|
||||||
use ruff_db::file_system::{FileSystemPath, MemoryFileSystem};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::vfs::{system_path_to_file, VfsFile};
|
use ruff_db::system::{MemoryFileSystem, SystemPath, TestSystem};
|
||||||
use ruff_db::Upcast;
|
use ruff_db::Upcast;
|
||||||
|
|
||||||
static FOO_CODE: &str = r#"
|
static FOO_CODE: &str = r#"
|
||||||
|
@ -47,16 +47,17 @@ def override(): ...
|
||||||
struct Case {
|
struct Case {
|
||||||
program: Program,
|
program: Program,
|
||||||
fs: MemoryFileSystem,
|
fs: MemoryFileSystem,
|
||||||
foo: VfsFile,
|
foo: File,
|
||||||
bar: VfsFile,
|
bar: File,
|
||||||
typing: VfsFile,
|
typing: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_case() -> Case {
|
fn setup_case() -> Case {
|
||||||
let fs = MemoryFileSystem::new();
|
let system = TestSystem::default();
|
||||||
let foo_path = FileSystemPath::new("/src/foo.py");
|
let fs = system.memory_file_system().clone();
|
||||||
let bar_path = FileSystemPath::new("/src/bar.py");
|
let foo_path = SystemPath::new("/src/foo.py");
|
||||||
let typing_path = FileSystemPath::new("/src/typing.pyi");
|
let bar_path = SystemPath::new("/src/bar.py");
|
||||||
|
let typing_path = SystemPath::new("/src/typing.pyi");
|
||||||
fs.write_files([
|
fs.write_files([
|
||||||
(foo_path, FOO_CODE),
|
(foo_path, FOO_CODE),
|
||||||
(bar_path, BAR_CODE),
|
(bar_path, BAR_CODE),
|
||||||
|
@ -64,10 +65,10 @@ fn setup_case() -> Case {
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let workspace_root = FileSystemPath::new("/src");
|
let workspace_root = SystemPath::new("/src");
|
||||||
let workspace = Workspace::new(workspace_root.to_path_buf());
|
let workspace = Workspace::new(workspace_root.to_path_buf());
|
||||||
|
|
||||||
let mut program = Program::new(workspace, fs.clone());
|
let mut program = Program::new(workspace, system);
|
||||||
let foo = system_path_to_file(&program, foo_path).unwrap();
|
let foo = system_path_to_file(&program, foo_path).unwrap();
|
||||||
|
|
||||||
set_module_resolution_settings(
|
set_module_resolution_settings(
|
||||||
|
@ -134,7 +135,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
|
||||||
|
|
||||||
case.fs
|
case.fs
|
||||||
.write_file(
|
.write_file(
|
||||||
FileSystemPath::new("/src/foo.py"),
|
SystemPath::new("/src/foo.py"),
|
||||||
format!("{BAR_CODE}\n# A comment\n"),
|
format!("{BAR_CODE}\n# A comment\n"),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -27,4 +27,3 @@ zip = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = { workspace = true }
|
insta = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
|
||||||
|
|
|
@ -3,13 +3,12 @@ use std::sync::Arc;
|
||||||
use countme::Count;
|
use countme::Count;
|
||||||
use dashmap::mapref::entry::Entry;
|
use dashmap::mapref::entry::Entry;
|
||||||
|
|
||||||
pub use crate::vendored::{VendoredPath, VendoredPathBuf};
|
pub use path::FilePath;
|
||||||
pub use path::VfsPath;
|
|
||||||
|
|
||||||
use crate::file_revision::FileRevision;
|
use crate::file_revision::FileRevision;
|
||||||
use crate::file_system::FileSystemPath;
|
use crate::files::private::FileStatus;
|
||||||
use crate::vendored::VendoredFileSystem;
|
use crate::system::SystemPath;
|
||||||
use crate::vfs::private::FileStatus;
|
use crate::vendored::VendoredPath;
|
||||||
use crate::{Db, FxDashMap};
|
use crate::{Db, FxDashMap};
|
||||||
|
|
||||||
mod path;
|
mod path;
|
||||||
|
@ -18,8 +17,8 @@ mod path;
|
||||||
///
|
///
|
||||||
/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory.
|
/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn system_path_to_file(db: &dyn Db, path: impl AsRef<FileSystemPath>) -> Option<VfsFile> {
|
pub fn system_path_to_file(db: &dyn Db, path: impl AsRef<SystemPath>) -> Option<File> {
|
||||||
let file = db.vfs().file_system(db, path.as_ref());
|
let file = db.files().system(db, path.as_ref());
|
||||||
|
|
||||||
// It's important that `vfs.file_system` creates a `VfsFile` even for files that don't exist or don't
|
// It's important that `vfs.file_system` creates a `VfsFile` even for files that don't exist or don't
|
||||||
// exist anymore so that Salsa can track that the caller of this function depends on the existence of
|
// exist anymore so that Salsa can track that the caller of this function depends on the existence of
|
||||||
|
@ -33,98 +32,53 @@ pub fn system_path_to_file(db: &dyn Db, path: impl AsRef<FileSystemPath>) -> Opt
|
||||||
|
|
||||||
/// Interns a vendored file path. Returns `Some` if the vendored file for `path` exists and `None` otherwise.
|
/// Interns a vendored file path. Returns `Some` if the vendored file for `path` exists and `None` otherwise.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn vendored_path_to_file(db: &dyn Db, path: impl AsRef<VendoredPath>) -> Option<VfsFile> {
|
pub fn vendored_path_to_file(db: &dyn Db, path: impl AsRef<VendoredPath>) -> Option<File> {
|
||||||
db.vfs().vendored(db, path.as_ref())
|
db.files().vendored(db, path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interns a virtual file system path and returns a salsa [`VfsFile`] ingredient.
|
/// Lookup table that maps [file paths](`FilePath`) to salsa interned [`File`] instances.
|
||||||
///
|
|
||||||
/// Returns `Some` if a file for `path` exists and is accessible by the user. Returns `None` otherwise.
|
|
||||||
///
|
|
||||||
/// See [`system_path_to_file`] and [`vendored_path_to_file`] if you always have either a file system or vendored path.
|
|
||||||
#[inline]
|
|
||||||
pub fn vfs_path_to_file(db: &dyn Db, path: &VfsPath) -> Option<VfsFile> {
|
|
||||||
match path {
|
|
||||||
VfsPath::FileSystem(path) => system_path_to_file(db, path),
|
|
||||||
VfsPath::Vendored(path) => vendored_path_to_file(db, path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Virtual file system that supports files from different sources.
|
|
||||||
///
|
|
||||||
/// The [`Vfs`] supports accessing files from:
|
|
||||||
///
|
|
||||||
/// * The file system
|
|
||||||
/// * Vendored files that are part of the distributed Ruff binary
|
|
||||||
///
|
|
||||||
/// ## Why do both the [`Vfs`] and [`FileSystem`](crate::FileSystem) trait exist?
|
|
||||||
///
|
|
||||||
/// It would have been an option to define [`FileSystem`](crate::FileSystem) in a way that all its operation accept
|
|
||||||
/// a [`VfsPath`]. This would have allowed to unify most of [`Vfs`] and [`FileSystem`](crate::FileSystem). The reason why they are
|
|
||||||
/// separate is that not all operations are supported for all [`VfsPath`]s:
|
|
||||||
///
|
|
||||||
/// * The only relevant operations for [`VendoredPath`]s are testing for existence and reading the content.
|
|
||||||
/// * The vendored file system is immutable and doesn't support writing nor does it require watching for changes.
|
|
||||||
/// * There's no requirement to walk the vendored typesystem.
|
|
||||||
///
|
|
||||||
/// The other reason is that most operations know if they are working with vendored or file system paths.
|
|
||||||
/// Requiring them to convert the path to an `VfsPath` to test if the file exist is cumbersome.
|
|
||||||
///
|
|
||||||
/// The main downside of the approach is that vendored files needs their own stubbing mechanism.
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Vfs {
|
pub struct Files {
|
||||||
inner: Arc<VfsInner>,
|
inner: Arc<FilesInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct VfsInner {
|
struct FilesInner {
|
||||||
/// Lookup table that maps [`VfsPath`]s to salsa interned [`VfsFile`] instances.
|
/// Lookup table that maps [`FilePath`]s to salsa interned [`File`] instances.
|
||||||
///
|
///
|
||||||
/// The map also stores entries for files that don't exist on the file system. This is necessary
|
/// The map also stores entries for files that don't exist on the file system. This is necessary
|
||||||
/// so that queries that depend on the existence of a file are re-executed when the file is created.
|
/// so that queries that depend on the existence of a file are re-executed when the file is created.
|
||||||
///
|
files_by_path: FxDashMap<FilePath, File>,
|
||||||
files_by_path: FxDashMap<VfsPath, VfsFile>,
|
|
||||||
vendored: VendoredVfs,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vfs {
|
impl Files {
|
||||||
/// Creates a new [`Vfs`] instance where the vendored files are stubbed out.
|
/// Looks up a file by its `path`.
|
||||||
pub fn with_stubbed_vendored() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Arc::new(VfsInner {
|
|
||||||
vendored: VendoredVfs::Stubbed(FxDashMap::default()),
|
|
||||||
..VfsInner::default()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Looks up a file by its path.
|
|
||||||
///
|
///
|
||||||
/// For a non-existing file, creates a new salsa [`VfsFile`] ingredient and stores it for future lookups.
|
/// For a non-existing file, creates a new salsa [`File`] ingredient and stores it for future lookups.
|
||||||
///
|
///
|
||||||
/// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory.
|
/// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory.
|
||||||
/// In these cases, a file with status [`FileStatus::Deleted`] is returned.
|
/// In these cases, a file with status [`FileStatus::Deleted`] is returned.
|
||||||
#[tracing::instrument(level = "debug", skip(self, db))]
|
#[tracing::instrument(level = "debug", skip(self, db))]
|
||||||
fn file_system(&self, db: &dyn Db, path: &FileSystemPath) -> VfsFile {
|
fn system(&self, db: &dyn Db, path: &SystemPath) -> File {
|
||||||
*self
|
*self
|
||||||
.inner
|
.inner
|
||||||
.files_by_path
|
.files_by_path
|
||||||
.entry(VfsPath::FileSystem(path.to_path_buf()))
|
.entry(FilePath::System(path.to_path_buf()))
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
let metadata = db.file_system().metadata(path);
|
let metadata = db.system().path_metadata(path);
|
||||||
|
|
||||||
match metadata {
|
match metadata {
|
||||||
Ok(metadata) if metadata.file_type().is_file() => VfsFile::new(
|
Ok(metadata) if metadata.file_type().is_file() => File::new(
|
||||||
db,
|
db,
|
||||||
VfsPath::FileSystem(path.to_path_buf()),
|
FilePath::System(path.to_path_buf()),
|
||||||
metadata.permissions(),
|
metadata.permissions(),
|
||||||
metadata.revision(),
|
metadata.revision(),
|
||||||
FileStatus::Exists,
|
FileStatus::Exists,
|
||||||
Count::default(),
|
Count::default(),
|
||||||
),
|
),
|
||||||
_ => VfsFile::new(
|
_ => File::new(
|
||||||
db,
|
db,
|
||||||
VfsPath::FileSystem(path.to_path_buf()),
|
FilePath::System(path.to_path_buf()),
|
||||||
None,
|
None,
|
||||||
FileRevision::zero(),
|
FileRevision::zero(),
|
||||||
FileStatus::Deleted,
|
FileStatus::Deleted,
|
||||||
|
@ -134,24 +88,32 @@ impl Vfs {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to look up the file for the given system path, returns `None` if no such file exists yet
|
||||||
|
fn try_system(&self, path: &SystemPath) -> Option<File> {
|
||||||
|
self.inner
|
||||||
|
.files_by_path
|
||||||
|
.get(&FilePath::System(path.to_path_buf()))
|
||||||
|
.map(|entry| *entry.value())
|
||||||
|
}
|
||||||
|
|
||||||
/// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path
|
/// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path
|
||||||
/// exists and `None` otherwise.
|
/// exists and `None` otherwise.
|
||||||
#[tracing::instrument(level = "debug", skip(self, db))]
|
#[tracing::instrument(level = "debug", skip(self, db))]
|
||||||
fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option<VfsFile> {
|
fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option<File> {
|
||||||
let file = match self
|
let file = match self
|
||||||
.inner
|
.inner
|
||||||
.files_by_path
|
.files_by_path
|
||||||
.entry(VfsPath::Vendored(path.to_path_buf()))
|
.entry(FilePath::Vendored(path.to_path_buf()))
|
||||||
{
|
{
|
||||||
Entry::Occupied(entry) => *entry.get(),
|
Entry::Occupied(entry) => *entry.get(),
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
let revision = self.inner.vendored.revision(path)?;
|
let metadata = db.vendored().metadata(path).ok()?;
|
||||||
|
|
||||||
let file = VfsFile::new(
|
let file = File::new(
|
||||||
db,
|
db,
|
||||||
VfsPath::Vendored(path.to_path_buf()),
|
FilePath::Vendored(path.to_path_buf()),
|
||||||
Some(0o444),
|
Some(0o444),
|
||||||
revision,
|
metadata.revision(),
|
||||||
FileStatus::Exists,
|
FileStatus::Exists,
|
||||||
Count::default(),
|
Count::default(),
|
||||||
);
|
);
|
||||||
|
@ -165,49 +127,16 @@ impl Vfs {
|
||||||
Some(file)
|
Some(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stubs out the vendored files with the given content.
|
/// Creates a salsa like snapshot. The instances share
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
/// If there are pending snapshots referencing this `Vfs` instance.
|
|
||||||
pub fn stub_vendored<P, S>(&mut self, vendored: impl IntoIterator<Item = (P, S)>)
|
|
||||||
where
|
|
||||||
P: AsRef<VendoredPath>,
|
|
||||||
S: ToString,
|
|
||||||
{
|
|
||||||
let inner = Arc::get_mut(&mut self.inner).unwrap();
|
|
||||||
|
|
||||||
let stubbed = FxDashMap::default();
|
|
||||||
|
|
||||||
for (path, content) in vendored {
|
|
||||||
stubbed.insert(path.as_ref().to_path_buf(), content.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
inner.vendored = VendoredVfs::Stubbed(stubbed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a salsa like snapshot of the files. The instances share
|
|
||||||
/// the same path-to-file mapping.
|
/// the same path-to-file mapping.
|
||||||
pub fn snapshot(&self) -> Self {
|
pub fn snapshot(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: self.inner.clone(),
|
inner: self.inner.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&self, db: &dyn Db, path: &VfsPath) -> String {
|
|
||||||
match path {
|
|
||||||
VfsPath::FileSystem(path) => db.file_system().read(path).unwrap_or_default(),
|
|
||||||
|
|
||||||
VfsPath::Vendored(vendored) => db
|
|
||||||
.vfs()
|
|
||||||
.inner
|
|
||||||
.vendored
|
|
||||||
.read(vendored)
|
|
||||||
.expect("Vendored file to exist"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Vfs {
|
impl std::fmt::Debug for Files {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut map = f.debug_map();
|
let mut map = f.debug_map();
|
||||||
|
|
||||||
|
@ -218,12 +147,13 @@ impl std::fmt::Debug for Vfs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A file that's either stored on the host system's file system or in the vendored file system.
|
||||||
#[salsa::input]
|
#[salsa::input]
|
||||||
pub struct VfsFile {
|
pub struct File {
|
||||||
/// The path of the file.
|
/// The path of the file.
|
||||||
#[id]
|
#[id]
|
||||||
#[return_ref]
|
#[return_ref]
|
||||||
pub path: VfsPath,
|
pub path: FilePath,
|
||||||
|
|
||||||
/// The unix permissions of the file. Only supported on unix systems. Always `None` on Windows
|
/// The unix permissions of the file. Only supported on unix systems. Always `None` on Windows
|
||||||
/// or when the file has been deleted.
|
/// or when the file has been deleted.
|
||||||
|
@ -234,17 +164,17 @@ pub struct VfsFile {
|
||||||
|
|
||||||
/// The status of the file.
|
/// The status of the file.
|
||||||
///
|
///
|
||||||
/// Salsa doesn't support deleting inputs. The only way to signal to the depending queries that
|
/// Salsa doesn't support deleting inputs. The only way to signal dependent queries that
|
||||||
/// the file has been deleted is to change the status to `Deleted`.
|
/// the file has been deleted is to change the status to `Deleted`.
|
||||||
status: FileStatus,
|
status: FileStatus,
|
||||||
|
|
||||||
/// Counter that counts the number of created file instances and active file instances.
|
/// Counter that counts the number of created file instances and active file instances.
|
||||||
/// Only enabled in debug builds.
|
/// Only enabled in debug builds.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
count: Count<VfsFile>,
|
count: Count<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfsFile {
|
impl File {
|
||||||
/// Reads the content of the file into a [`String`].
|
/// Reads the content of the file into a [`String`].
|
||||||
///
|
///
|
||||||
/// Reading the same file multiple times isn't guaranteed to return the same content. It's possible
|
/// Reading the same file multiple times isn't guaranteed to return the same content. It's possible
|
||||||
|
@ -253,21 +183,26 @@ impl VfsFile {
|
||||||
/// an empty string, which is the closest to the content that the file contains now. Returning
|
/// an empty string, which is the closest to the content that the file contains now. Returning
|
||||||
/// an empty string shouldn't be a problem because the query will be re-executed as soon as the
|
/// an empty string shouldn't be a problem because the query will be re-executed as soon as the
|
||||||
/// changes are applied to the database.
|
/// changes are applied to the database.
|
||||||
pub(crate) fn read(&self, db: &dyn Db) -> String {
|
pub(crate) fn read_to_string(&self, db: &dyn Db) -> String {
|
||||||
let path = self.path(db);
|
let path = self.path(db);
|
||||||
|
|
||||||
if path.is_file_system_path() {
|
let result = match path {
|
||||||
|
FilePath::System(system) => {
|
||||||
// Add a dependency on the revision to ensure the operation gets re-executed when the file changes.
|
// Add a dependency on the revision to ensure the operation gets re-executed when the file changes.
|
||||||
let _ = self.revision(db);
|
let _ = self.revision(db);
|
||||||
}
|
|
||||||
|
|
||||||
db.vfs().read(db, path)
|
db.system().read_to_string(system)
|
||||||
|
}
|
||||||
|
FilePath::Vendored(vendored) => db.vendored().read_to_string(vendored),
|
||||||
|
};
|
||||||
|
|
||||||
|
result.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refreshes the file metadata by querying the file system if needed.
|
/// Refreshes the file metadata by querying the file system if needed.
|
||||||
/// TODO: The API should instead take all observed changes from the file system directly
|
/// TODO: The API should instead take all observed changes from the file system directly
|
||||||
/// and then apply the VfsFile status accordingly. But for now, this is sufficient.
|
/// and then apply the VfsFile status accordingly. But for now, this is sufficient.
|
||||||
pub fn touch_path(db: &mut dyn Db, path: &VfsPath) {
|
pub fn touch_path(db: &mut dyn Db, path: &FilePath) {
|
||||||
Self::touch_impl(db, path, None);
|
Self::touch_impl(db, path, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,10 +212,10 @@ impl VfsFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Private method providing the implementation for [`Self::touch_path`] and [`Self::touch`].
|
/// Private method providing the implementation for [`Self::touch_path`] and [`Self::touch`].
|
||||||
fn touch_impl(db: &mut dyn Db, path: &VfsPath, file: Option<VfsFile>) {
|
fn touch_impl(db: &mut dyn Db, path: &FilePath, file: Option<File>) {
|
||||||
match path {
|
match path {
|
||||||
VfsPath::FileSystem(path) => {
|
FilePath::System(path) => {
|
||||||
let metadata = db.file_system().metadata(path);
|
let metadata = db.system().path_metadata(path);
|
||||||
|
|
||||||
let (status, revision) = match metadata {
|
let (status, revision) = match metadata {
|
||||||
Ok(metadata) if metadata.file_type().is_file() => {
|
Ok(metadata) if metadata.file_type().is_file() => {
|
||||||
|
@ -289,59 +224,20 @@ impl VfsFile {
|
||||||
_ => (FileStatus::Deleted, FileRevision::zero()),
|
_ => (FileStatus::Deleted, FileRevision::zero()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let file = file.unwrap_or_else(|| db.vfs().file_system(db, path));
|
let Some(file) = file.or_else(|| db.files().try_system(path)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
file.set_status(db).to(status);
|
file.set_status(db).to(status);
|
||||||
file.set_revision(db).to(revision);
|
file.set_revision(db).to(revision);
|
||||||
}
|
}
|
||||||
VfsPath::Vendored(_) => {
|
FilePath::Vendored(_) => {
|
||||||
// Readonly, can never be out of date.
|
// Readonly, can never be out of date.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum VendoredVfs {
|
|
||||||
#[allow(unused)]
|
|
||||||
Real(VendoredFileSystem),
|
|
||||||
Stubbed(FxDashMap<VendoredPathBuf, String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VendoredVfs {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Stubbed(FxDashMap::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VendoredVfs {
|
|
||||||
fn revision(&self, path: &VendoredPath) -> Option<FileRevision> {
|
|
||||||
match self {
|
|
||||||
VendoredVfs::Real(file_system) => file_system
|
|
||||||
.metadata(path)
|
|
||||||
.map(|metadata| metadata.revision()),
|
|
||||||
VendoredVfs::Stubbed(stubbed) => stubbed
|
|
||||||
.contains_key(&path.to_path_buf())
|
|
||||||
.then_some(FileRevision::new(1)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&self, path: &VendoredPath) -> std::io::Result<String> {
|
|
||||||
match self {
|
|
||||||
VendoredVfs::Real(file_system) => file_system.read(path),
|
|
||||||
VendoredVfs::Stubbed(stubbed) => {
|
|
||||||
if let Some(contents) = stubbed.get(&path.to_path_buf()).as_deref().cloned() {
|
|
||||||
Ok(contents)
|
|
||||||
} else {
|
|
||||||
Err(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::NotFound,
|
|
||||||
format!("Could not find file {path:?}"),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The types in here need to be public because they're salsa ingredients but we
|
// The types in here need to be public because they're salsa ingredients but we
|
||||||
// don't want them to be publicly accessible. That's why we put them into a private module.
|
// don't want them to be publicly accessible. That's why we put them into a private module.
|
||||||
mod private {
|
mod private {
|
||||||
|
@ -358,21 +254,22 @@ mod private {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::file_revision::FileRevision;
|
use crate::file_revision::FileRevision;
|
||||||
|
use crate::files::{system_path_to_file, vendored_path_to_file};
|
||||||
|
use crate::system::DbWithTestSystem;
|
||||||
use crate::tests::TestDb;
|
use crate::tests::TestDb;
|
||||||
use crate::vfs::{system_path_to_file, vendored_path_to_file};
|
use crate::vendored::tests::VendoredFileSystemBuilder;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn file_system_existing_file() -> crate::file_system::Result<()> {
|
fn file_system_existing_file() -> crate::system::Result<()> {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file("test.py", "print('Hello world')")?;
|
||||||
.write_files([("test.py", "print('Hello world')")])?;
|
|
||||||
|
|
||||||
let test = system_path_to_file(&db, "test.py").expect("File to exist.");
|
let test = system_path_to_file(&db, "test.py").expect("File to exist.");
|
||||||
|
|
||||||
assert_eq!(test.permissions(&db), Some(0o755));
|
assert_eq!(test.permissions(&db), Some(0o755));
|
||||||
assert_ne!(test.revision(&db), FileRevision::zero());
|
assert_ne!(test.revision(&db), FileRevision::zero());
|
||||||
assert_eq!(&test.read(&db), "print('Hello world')");
|
assert_eq!(&test.read_to_string(&db), "print('Hello world')");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -390,14 +287,18 @@ mod tests {
|
||||||
fn stubbed_vendored_file() {
|
fn stubbed_vendored_file() {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
|
|
||||||
db.vfs_mut()
|
let mut vendored_builder = VendoredFileSystemBuilder::new();
|
||||||
.stub_vendored([("test.py", "def foo() -> str")]);
|
vendored_builder
|
||||||
|
.add_file("test.pyi", "def foo() -> str")
|
||||||
|
.unwrap();
|
||||||
|
let vendored = vendored_builder.finish().unwrap();
|
||||||
|
db.with_vendored(vendored);
|
||||||
|
|
||||||
let test = vendored_path_to_file(&db, "test.py").expect("Vendored file to exist.");
|
let test = vendored_path_to_file(&db, "test.pyi").expect("Vendored file to exist.");
|
||||||
|
|
||||||
assert_eq!(test.permissions(&db), Some(0o444));
|
assert_eq!(test.permissions(&db), Some(0o444));
|
||||||
assert_ne!(test.revision(&db), FileRevision::zero());
|
assert_ne!(test.revision(&db), FileRevision::zero());
|
||||||
assert_eq!(&test.read(&db), "def foo() -> str");
|
assert_eq!(&test.read_to_string(&db), "def foo() -> str");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
176
crates/ruff_db/src/files/path.rs
Normal file
176
crates/ruff_db/src/files/path.rs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
use crate::files::{system_path_to_file, vendored_path_to_file, File};
|
||||||
|
use crate::system::{SystemPath, SystemPathBuf};
|
||||||
|
use crate::vendored::{VendoredPath, VendoredPathBuf};
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
|
/// Path to a file.
|
||||||
|
///
|
||||||
|
/// The path abstracts that files in Ruff can come from different sources:
|
||||||
|
///
|
||||||
|
/// * a file stored on the [host system](crate::system::System).
|
||||||
|
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub enum FilePath {
|
||||||
|
/// Path to a file on the [host system](crate::system::System).
|
||||||
|
System(SystemPathBuf),
|
||||||
|
/// Path to a file vendored as part of Ruff. Stored in the [vendored file system](crate::vendored::VendoredFileSystem).
|
||||||
|
Vendored(VendoredPathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilePath {
|
||||||
|
/// Create a new path to a file on the file system.
|
||||||
|
#[must_use]
|
||||||
|
pub fn system(path: impl AsRef<SystemPath>) -> Self {
|
||||||
|
FilePath::System(path.as_ref().to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `Some` if the path is a file system path that points to a path on disk.
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn into_system_path_buf(self) -> Option<SystemPathBuf> {
|
||||||
|
match self {
|
||||||
|
FilePath::System(path) => Some(path),
|
||||||
|
FilePath::Vendored(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn as_system_path(&self) -> Option<&SystemPath> {
|
||||||
|
match self {
|
||||||
|
FilePath::System(path) => Some(path.as_path()),
|
||||||
|
FilePath::Vendored(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the path is a file system path that points to a path on disk.
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub const fn is_system_path(&self) -> bool {
|
||||||
|
matches!(self, FilePath::System(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the path is a vendored path.
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub const fn is_vendored_path(&self) -> bool {
|
||||||
|
matches!(self, FilePath::Vendored(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn as_vendored_path(&self) -> Option<&VendoredPath> {
|
||||||
|
match self {
|
||||||
|
FilePath::Vendored(path) => Some(path.as_path()),
|
||||||
|
FilePath::System(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yields the underlying [`str`] slice.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
FilePath::System(path) => path.as_str(),
|
||||||
|
FilePath::Vendored(path) => path.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interns a virtual file system path and returns a salsa [`File`] ingredient.
|
||||||
|
///
|
||||||
|
/// Returns `Some` if a file for `path` exists and is accessible by the user. Returns `None` otherwise.
|
||||||
|
///
|
||||||
|
/// See [`system_path_to_file`] and [`vendored_path_to_file`] if you always have either a file system or vendored path.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_file(&self, db: &dyn Db) -> Option<File> {
|
||||||
|
match self {
|
||||||
|
FilePath::System(path) => system_path_to_file(db, path),
|
||||||
|
FilePath::Vendored(path) => vendored_path_to_file(db, path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for FilePath {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SystemPathBuf> for FilePath {
|
||||||
|
fn from(value: SystemPathBuf) -> Self {
|
||||||
|
Self::System(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SystemPath> for FilePath {
|
||||||
|
fn from(value: &SystemPath) -> Self {
|
||||||
|
FilePath::System(value.to_path_buf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VendoredPathBuf> for FilePath {
|
||||||
|
fn from(value: VendoredPathBuf) -> Self {
|
||||||
|
Self::Vendored(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&VendoredPath> for FilePath {
|
||||||
|
fn from(value: &VendoredPath) -> Self {
|
||||||
|
Self::Vendored(value.to_path_buf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<SystemPath> for FilePath {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &SystemPath) -> bool {
|
||||||
|
self.as_system_path()
|
||||||
|
.is_some_and(|self_path| self_path == other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<FilePath> for SystemPath {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &FilePath) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<SystemPathBuf> for FilePath {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &SystemPathBuf) -> bool {
|
||||||
|
self == other.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<FilePath> for SystemPathBuf {
|
||||||
|
fn eq(&self, other: &FilePath) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<VendoredPath> for FilePath {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &VendoredPath) -> bool {
|
||||||
|
self.as_vendored_path()
|
||||||
|
.is_some_and(|self_path| self_path == other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<FilePath> for VendoredPath {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &FilePath) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<VendoredPathBuf> for FilePath {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &VendoredPathBuf) -> bool {
|
||||||
|
other.as_path() == self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<FilePath> for VendoredPathBuf {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &FilePath) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,28 +3,29 @@ use std::hash::BuildHasherDefault;
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use salsa::DbWithJar;
|
use salsa::DbWithJar;
|
||||||
|
|
||||||
use crate::file_system::FileSystem;
|
use crate::files::{File, Files};
|
||||||
use crate::parsed::parsed_module;
|
use crate::parsed::parsed_module;
|
||||||
use crate::source::{line_index, source_text};
|
use crate::source::{line_index, source_text};
|
||||||
use crate::vfs::{Vfs, VfsFile};
|
use crate::system::System;
|
||||||
|
use crate::vendored::VendoredFileSystem;
|
||||||
|
|
||||||
pub mod file_revision;
|
pub mod file_revision;
|
||||||
pub mod file_system;
|
pub mod files;
|
||||||
pub mod parsed;
|
pub mod parsed;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
|
pub mod system;
|
||||||
pub mod vendored;
|
pub mod vendored;
|
||||||
pub mod vfs;
|
|
||||||
|
|
||||||
pub(crate) type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
pub(crate) type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||||
|
|
||||||
#[salsa::jar(db=Db)]
|
#[salsa::jar(db=Db)]
|
||||||
pub struct Jar(VfsFile, source_text, line_index, parsed_module);
|
pub struct Jar(File, source_text, line_index, parsed_module);
|
||||||
|
|
||||||
/// Database that gives access to the virtual filesystem, source code, and parsed AST.
|
/// Most basic database that gives access to files, the host system, source code, and parsed AST.
|
||||||
pub trait Db: DbWithJar<Jar> {
|
pub trait Db: DbWithJar<Jar> {
|
||||||
fn file_system(&self) -> &dyn FileSystem;
|
fn vendored(&self) -> &VendoredFileSystem;
|
||||||
|
fn system(&self) -> &dyn System;
|
||||||
fn vfs(&self) -> &Vfs;
|
fn files(&self) -> &Files;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for upcasting a reference to a base trait object.
|
/// Trait for upcasting a reference to a base trait object.
|
||||||
|
@ -38,39 +39,36 @@ mod tests {
|
||||||
|
|
||||||
use salsa::DebugWithDb;
|
use salsa::DebugWithDb;
|
||||||
|
|
||||||
use crate::file_system::{FileSystem, MemoryFileSystem};
|
use crate::files::Files;
|
||||||
use crate::vfs::{VendoredPathBuf, Vfs};
|
use crate::system::TestSystem;
|
||||||
|
use crate::system::{DbWithTestSystem, System};
|
||||||
|
use crate::vendored::VendoredFileSystem;
|
||||||
use crate::{Db, Jar};
|
use crate::{Db, Jar};
|
||||||
|
|
||||||
/// Database that can be used for testing.
|
/// Database that can be used for testing.
|
||||||
///
|
///
|
||||||
/// Uses an in memory filesystem and it stubs out the vendored files by default.
|
/// Uses an in memory filesystem and it stubs out the vendored files by default.
|
||||||
|
#[derive(Default)]
|
||||||
#[salsa::db(Jar)]
|
#[salsa::db(Jar)]
|
||||||
pub(crate) struct TestDb {
|
pub(crate) struct TestDb {
|
||||||
storage: salsa::Storage<Self>,
|
storage: salsa::Storage<Self>,
|
||||||
vfs: Vfs,
|
files: Files,
|
||||||
file_system: MemoryFileSystem,
|
system: TestSystem,
|
||||||
|
vendored: VendoredFileSystem,
|
||||||
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestDb {
|
impl TestDb {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let mut vfs = Vfs::default();
|
|
||||||
vfs.stub_vendored::<VendoredPathBuf, String>([]);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
storage: salsa::Storage::default(),
|
storage: salsa::Storage::default(),
|
||||||
file_system: MemoryFileSystem::default(),
|
system: TestSystem::default(),
|
||||||
|
vendored: VendoredFileSystem::default(),
|
||||||
events: std::sync::Arc::default(),
|
events: std::sync::Arc::default(),
|
||||||
vfs,
|
files: Files::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn file_system(&self) -> &MemoryFileSystem {
|
|
||||||
&self.file_system
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Empties the internal store of salsa events that have been emitted,
|
/// Empties the internal store of salsa events that have been emitted,
|
||||||
/// and returns them as a `Vec` (equivalent to [`std::mem::take`]).
|
/// and returns them as a `Vec` (equivalent to [`std::mem::take`]).
|
||||||
///
|
///
|
||||||
|
@ -93,22 +91,32 @@ mod tests {
|
||||||
self.take_salsa_events();
|
self.take_salsa_events();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn file_system_mut(&mut self) -> &mut MemoryFileSystem {
|
pub(crate) fn with_vendored(&mut self, vendored_file_system: VendoredFileSystem) {
|
||||||
&mut self.file_system
|
self.vendored = vendored_file_system;
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn vfs_mut(&mut self) -> &mut Vfs {
|
|
||||||
&mut self.vfs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db for TestDb {
|
impl Db for TestDb {
|
||||||
fn file_system(&self) -> &dyn FileSystem {
|
fn vendored(&self) -> &VendoredFileSystem {
|
||||||
&self.file_system
|
&self.vendored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vfs(&self) -> &Vfs {
|
fn system(&self) -> &dyn System {
|
||||||
&self.vfs
|
&self.system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn files(&self) -> &Files {
|
||||||
|
&self.files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbWithTestSystem for TestDb {
|
||||||
|
fn test_system(&self) -> &TestSystem {
|
||||||
|
&self.system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_system_mut(&mut self) -> &mut TestSystem {
|
||||||
|
&mut self.system
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +132,10 @@ mod tests {
|
||||||
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
||||||
salsa::Snapshot::new(Self {
|
salsa::Snapshot::new(Self {
|
||||||
storage: self.storage.snapshot(),
|
storage: self.storage.snapshot(),
|
||||||
file_system: self.file_system.snapshot(),
|
system: self.system.snapshot(),
|
||||||
vfs: self.vfs.snapshot(),
|
files: self.files.snapshot(),
|
||||||
events: self.events.clone(),
|
events: self.events.clone(),
|
||||||
|
vendored: self.vendored.snapshot(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ use std::sync::Arc;
|
||||||
use ruff_python_ast::{ModModule, PySourceType};
|
use ruff_python_ast::{ModModule, PySourceType};
|
||||||
use ruff_python_parser::{parse_unchecked_source, Parsed};
|
use ruff_python_parser::{parse_unchecked_source, Parsed};
|
||||||
|
|
||||||
|
use crate::files::{File, FilePath};
|
||||||
use crate::source::source_text;
|
use crate::source::source_text;
|
||||||
use crate::vfs::{VfsFile, VfsPath};
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
/// Returns the parsed AST of `file`, including its token stream.
|
/// Returns the parsed AST of `file`, including its token stream.
|
||||||
///
|
///
|
||||||
/// The query uses Ruff's error-resilient parser. That means that the parser always succeeds to produce a
|
/// The query uses Ruff's error-resilient parser. That means that the parser always succeeds to produce an
|
||||||
/// AST even if the file contains syntax errors. The parse errors
|
/// AST even if the file contains syntax errors. The parse errors
|
||||||
/// are then accessible through [`Parsed::errors`].
|
/// are then accessible through [`Parsed::errors`].
|
||||||
///
|
///
|
||||||
|
@ -21,17 +21,17 @@ use crate::Db;
|
||||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Sala requires
|
/// The other reason is that Ruff's AST doesn't implement `Eq` which Sala requires
|
||||||
/// for determining if a query result is unchanged.
|
/// for determining if a query result is unchanged.
|
||||||
#[salsa::tracked(return_ref, no_eq)]
|
#[salsa::tracked(return_ref, no_eq)]
|
||||||
pub fn parsed_module(db: &dyn Db, file: VfsFile) -> ParsedModule {
|
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||||
let _span = tracing::trace_span!("parse_module", file = ?file).entered();
|
let _span = tracing::trace_span!("parse_module", file = ?file).entered();
|
||||||
|
|
||||||
let source = source_text(db, file);
|
let source = source_text(db, file);
|
||||||
let path = file.path(db);
|
let path = file.path(db);
|
||||||
|
|
||||||
let ty = match path {
|
let ty = match path {
|
||||||
VfsPath::FileSystem(path) => path
|
FilePath::System(path) => path
|
||||||
.extension()
|
.extension()
|
||||||
.map_or(PySourceType::Python, PySourceType::from_extension),
|
.map_or(PySourceType::Python, PySourceType::from_extension),
|
||||||
VfsPath::Vendored(_) => PySourceType::Stub,
|
FilePath::Vendored(_) => PySourceType::Stub,
|
||||||
};
|
};
|
||||||
|
|
||||||
ParsedModule::new(parse_unchecked_source(&source, ty))
|
ParsedModule::new(parse_unchecked_source(&source, ty))
|
||||||
|
@ -72,19 +72,18 @@ impl std::fmt::Debug for ParsedModule {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::file_system::FileSystemPath;
|
use crate::files::{system_path_to_file, vendored_path_to_file};
|
||||||
use crate::parsed::parsed_module;
|
use crate::parsed::parsed_module;
|
||||||
|
use crate::system::{DbWithTestSystem, SystemPath};
|
||||||
use crate::tests::TestDb;
|
use crate::tests::TestDb;
|
||||||
use crate::vendored::VendoredPath;
|
use crate::vendored::{tests::VendoredFileSystemBuilder, VendoredPath};
|
||||||
use crate::vfs::{system_path_to_file, vendored_path_to_file};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_file() -> crate::file_system::Result<()> {
|
fn python_file() -> crate::system::Result<()> {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = "test.py";
|
let path = "test.py";
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file(path, "x = 10".to_string())?;
|
||||||
.write_file(path, "x = 10".to_string())?;
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
|
@ -96,12 +95,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_ipynb_file() -> crate::file_system::Result<()> {
|
fn python_ipynb_file() -> crate::system::Result<()> {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = FileSystemPath::new("test.ipynb");
|
let path = SystemPath::new("test.ipynb");
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file(path, "%timeit a = b".to_string())?;
|
||||||
.write_file(path, "%timeit a = b".to_string())?;
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
|
@ -115,7 +113,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn vendored_file() {
|
fn vendored_file() {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
db.vfs_mut().stub_vendored([(
|
|
||||||
|
let mut vendored_builder = VendoredFileSystemBuilder::new();
|
||||||
|
vendored_builder
|
||||||
|
.add_file(
|
||||||
"path.pyi",
|
"path.pyi",
|
||||||
r#"
|
r#"
|
||||||
import sys
|
import sys
|
||||||
|
@ -126,7 +127,10 @@ if sys.platform == "win32":
|
||||||
else:
|
else:
|
||||||
from posixpath import *
|
from posixpath import *
|
||||||
from posixpath import __all__ as __all__"#,
|
from posixpath import __all__ as __all__"#,
|
||||||
)]);
|
)
|
||||||
|
.unwrap();
|
||||||
|
let vendored = vendored_builder.finish().unwrap();
|
||||||
|
db.with_vendored(vendored);
|
||||||
|
|
||||||
let file = vendored_path_to_file(&db, VendoredPath::new("path.pyi")).unwrap();
|
let file = vendored_path_to_file(&db, VendoredPath::new("path.pyi")).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,15 @@ use salsa::DebugWithDb;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::vfs::VfsFile;
|
use crate::files::File;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
/// Reads the content of file.
|
/// Reads the content of file.
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub fn source_text(db: &dyn Db, file: VfsFile) -> SourceText {
|
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||||
let _span = tracing::trace_span!("source_text", ?file).entered();
|
let _span = tracing::trace_span!("source_text", ?file).entered();
|
||||||
|
|
||||||
let content = file.read(db);
|
let content = file.read_to_string(db);
|
||||||
|
|
||||||
SourceText {
|
SourceText {
|
||||||
inner: Arc::from(content),
|
inner: Arc::from(content),
|
||||||
|
@ -22,7 +22,7 @@ pub fn source_text(db: &dyn Db, file: VfsFile) -> SourceText {
|
||||||
|
|
||||||
/// Computes the [`LineIndex`] for `file`.
|
/// Computes the [`LineIndex`] for `file`.
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub fn line_index(db: &dyn Db, file: VfsFile) -> LineIndex {
|
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
|
||||||
let _span = tracing::trace_span!("line_index", file = ?file.debug(db)).entered();
|
let _span = tracing::trace_span!("line_index", file = ?file.debug(db)).entered();
|
||||||
|
|
||||||
let source = source_text(db, file);
|
let source = source_text(db, file);
|
||||||
|
@ -30,7 +30,7 @@ pub fn line_index(db: &dyn Db, file: VfsFile) -> LineIndex {
|
||||||
LineIndex::from_source_text(&source)
|
LineIndex::from_source_text(&source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The source text of a [`VfsFile`].
|
/// The source text of a [`File`].
|
||||||
///
|
///
|
||||||
/// Cheap cloneable in `O(1)`.
|
/// Cheap cloneable in `O(1)`.
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
@ -63,30 +63,25 @@ impl std::fmt::Debug for SourceText {
|
||||||
mod tests {
|
mod tests {
|
||||||
use salsa::EventKind;
|
use salsa::EventKind;
|
||||||
|
|
||||||
|
use crate::files::system_path_to_file;
|
||||||
|
use crate::source::{line_index, source_text};
|
||||||
|
use crate::system::{DbWithTestSystem, SystemPath};
|
||||||
|
use crate::tests::TestDb;
|
||||||
use ruff_source_file::OneIndexed;
|
use ruff_source_file::OneIndexed;
|
||||||
use ruff_text_size::TextSize;
|
use ruff_text_size::TextSize;
|
||||||
|
|
||||||
use crate::file_system::FileSystemPath;
|
|
||||||
use crate::source::{line_index, source_text};
|
|
||||||
use crate::tests::TestDb;
|
|
||||||
use crate::vfs::system_path_to_file;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn re_runs_query_when_file_revision_changes() -> crate::file_system::Result<()> {
|
fn re_runs_query_when_file_revision_changes() -> crate::system::Result<()> {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = FileSystemPath::new("test.py");
|
let path = SystemPath::new("test.py");
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file(path, "x = 10".to_string())?;
|
||||||
.write_file(path, "x = 10".to_string())?;
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
assert_eq!(&*source_text(&db, file), "x = 10");
|
assert_eq!(&*source_text(&db, file), "x = 10");
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file(path, "x = 20".to_string()).unwrap();
|
||||||
.write_file(path, "x = 20".to_string())
|
|
||||||
.unwrap();
|
|
||||||
file.touch(&mut db);
|
|
||||||
|
|
||||||
assert_eq!(&*source_text(&db, file), "x = 20");
|
assert_eq!(&*source_text(&db, file), "x = 20");
|
||||||
|
|
||||||
|
@ -94,12 +89,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn text_is_cached_if_revision_is_unchanged() -> crate::file_system::Result<()> {
|
fn text_is_cached_if_revision_is_unchanged() -> crate::system::Result<()> {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = FileSystemPath::new("test.py");
|
let path = SystemPath::new("test.py");
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file(path, "x = 10".to_string())?;
|
||||||
.write_file(path, "x = 10".to_string())?;
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
|
|
||||||
|
@ -121,12 +115,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn line_index_for_source() -> crate::file_system::Result<()> {
|
fn line_index_for_source() -> crate::system::Result<()> {
|
||||||
let mut db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
let path = FileSystemPath::new("test.py");
|
let path = SystemPath::new("test.py");
|
||||||
|
|
||||||
db.file_system_mut()
|
db.write_file(path, "x = 10\ny = 20".to_string())?;
|
||||||
.write_file(path, "x = 10\ny = 20".to_string())?;
|
|
||||||
|
|
||||||
let file = system_path_to_file(&db, path).unwrap();
|
let file = system_path_to_file(&db, path).unwrap();
|
||||||
let index = line_index(&db, file);
|
let index = line_index(&db, file);
|
||||||
|
|
97
crates/ruff_db/src/system.rs
Normal file
97
crates/ruff_db/src/system.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
pub use memory_fs::MemoryFileSystem;
|
||||||
|
pub use os::OsSystem;
|
||||||
|
pub use test::{DbWithTestSystem, TestSystem};
|
||||||
|
|
||||||
|
use crate::file_revision::FileRevision;
|
||||||
|
|
||||||
|
pub use self::path::{SystemPath, SystemPathBuf};
|
||||||
|
|
||||||
|
mod memory_fs;
|
||||||
|
mod os;
|
||||||
|
mod path;
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub type Result<T> = std::io::Result<T>;
|
||||||
|
|
||||||
|
/// The system on which Ruff runs.
|
||||||
|
///
|
||||||
|
/// Ruff supports running on the CLI, in a language server, and in a browser (WASM). Each of these
|
||||||
|
/// host-systems differ in what system operations they support and how they interact with the file system:
|
||||||
|
/// * Language server:
|
||||||
|
/// * Reading a file's content should take into account that it might have unsaved changes because it's open in the editor.
|
||||||
|
/// * Use structured representations for notebooks, making deserializing a notebook from a string unnecessary.
|
||||||
|
/// * Use their own file watching infrastructure.
|
||||||
|
/// * WASM (Browser):
|
||||||
|
/// * There are ways to emulate a file system in WASM but a native memory-filesystem is more efficient.
|
||||||
|
/// * Doesn't support a current working directory
|
||||||
|
/// * File watching isn't supported.
|
||||||
|
///
|
||||||
|
/// Abstracting the system also enables tests to use a more efficient in-memory file system.
|
||||||
|
pub trait System {
|
||||||
|
/// Reads the metadata of the file or directory at `path`.
|
||||||
|
fn path_metadata(&self, path: &SystemPath) -> Result<Metadata>;
|
||||||
|
|
||||||
|
/// Reads the content of the file at `path` into a [`String`].
|
||||||
|
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
|
||||||
|
|
||||||
|
/// Returns `true` if `path` exists.
|
||||||
|
fn path_exists(&self, path: &SystemPath) -> bool {
|
||||||
|
self.path_metadata(path).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `path` exists and is a directory.
|
||||||
|
fn is_directory(&self, path: &SystemPath) -> bool {
|
||||||
|
self.path_metadata(path)
|
||||||
|
.map_or(false, |metadata| metadata.file_type.is_directory())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `path` exists and is a file.
|
||||||
|
fn is_file(&self, path: &SystemPath) -> bool {
|
||||||
|
self.path_metadata(path)
|
||||||
|
.map_or(false, |metadata| metadata.file_type.is_file())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Metadata {
|
||||||
|
revision: FileRevision,
|
||||||
|
permissions: Option<u32>,
|
||||||
|
file_type: FileType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
pub fn revision(&self) -> FileRevision {
|
||||||
|
self.revision
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn permissions(&self) -> Option<u32> {
|
||||||
|
self.permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_type(&self) -> FileType {
|
||||||
|
self.file_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
||||||
|
pub enum FileType {
|
||||||
|
File,
|
||||||
|
Directory,
|
||||||
|
Symlink,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileType {
|
||||||
|
pub const fn is_file(self) -> bool {
|
||||||
|
matches!(self, FileType::File)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_directory(self) -> bool {
|
||||||
|
matches!(self, FileType::Directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_symlink(self) -> bool {
|
||||||
|
matches!(self, FileType::Symlink)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use std::sync::{Arc, RwLock, RwLockWriteGuard};
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
|
|
||||||
use crate::file_system::{FileSystem, FileSystemPath, FileType, Metadata, Result};
|
use crate::system::{FileType, Metadata, Result, SystemPath};
|
||||||
|
|
||||||
/// File system that stores all content in memory.
|
/// File system that stores all content in memory.
|
||||||
///
|
///
|
||||||
|
@ -16,9 +16,7 @@ use crate::file_system::{FileSystem, FileSystemPath, FileType, Metadata, Result}
|
||||||
/// * hardlinks
|
/// * hardlinks
|
||||||
/// * permissions: All files and directories have the permission 0755.
|
/// * permissions: All files and directories have the permission 0755.
|
||||||
///
|
///
|
||||||
/// Use a tempdir with the real file system to test these advanced file system features and complex file system behavior.
|
/// Use a tempdir with the real file system to test these advanced file system features and behavior.
|
||||||
///
|
|
||||||
/// Only intended for testing purposes.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MemoryFileSystem {
|
pub struct MemoryFileSystem {
|
||||||
inner: Arc<MemoryFileSystemInner>,
|
inner: Arc<MemoryFileSystemInner>,
|
||||||
|
@ -32,7 +30,8 @@ impl MemoryFileSystem {
|
||||||
Self::with_cwd("/")
|
Self::with_cwd("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_cwd(cwd: impl AsRef<FileSystemPath>) -> Self {
|
/// Creates a new, empty in memory file system with the given current working directory.
|
||||||
|
pub fn with_cwd(cwd: impl AsRef<SystemPath>) -> Self {
|
||||||
let cwd = Utf8PathBuf::from(cwd.as_ref().as_str());
|
let cwd = Utf8PathBuf::from(cwd.as_ref().as_str());
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -47,7 +46,7 @@ impl MemoryFileSystem {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.create_directory_all(FileSystemPath::new(&cwd)).unwrap();
|
fs.create_directory_all(SystemPath::new(&cwd)).unwrap();
|
||||||
|
|
||||||
fs
|
fs
|
||||||
}
|
}
|
||||||
|
@ -59,6 +58,69 @@ impl MemoryFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn metadata(&self, path: impl AsRef<SystemPath>) -> Result<Metadata> {
|
||||||
|
fn metadata(fs: &MemoryFileSystemInner, path: &SystemPath) -> Result<Metadata> {
|
||||||
|
let by_path = fs.by_path.read().unwrap();
|
||||||
|
let normalized = normalize_path(path, &fs.cwd);
|
||||||
|
|
||||||
|
let entry = by_path.get(&normalized).ok_or_else(not_found)?;
|
||||||
|
|
||||||
|
let metadata = match entry {
|
||||||
|
Entry::File(file) => Metadata {
|
||||||
|
revision: file.last_modified.into(),
|
||||||
|
permissions: Some(MemoryFileSystem::PERMISSION),
|
||||||
|
file_type: FileType::File,
|
||||||
|
},
|
||||||
|
Entry::Directory(directory) => Metadata {
|
||||||
|
revision: directory.last_modified.into(),
|
||||||
|
permissions: Some(MemoryFileSystem::PERMISSION),
|
||||||
|
file_type: FileType::Directory,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata(&self.inner, path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_file(&self, path: impl AsRef<SystemPath>) -> bool {
|
||||||
|
let by_path = self.inner.by_path.read().unwrap();
|
||||||
|
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
||||||
|
|
||||||
|
matches!(by_path.get(&normalized), Some(Entry::File(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_directory(&self, path: impl AsRef<SystemPath>) -> bool {
|
||||||
|
let by_path = self.inner.by_path.read().unwrap();
|
||||||
|
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
||||||
|
|
||||||
|
matches!(by_path.get(&normalized), Some(Entry::Directory(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
|
||||||
|
fn read_to_string(fs: &MemoryFileSystemInner, path: &SystemPath) -> Result<String> {
|
||||||
|
let by_path = fs.by_path.read().unwrap();
|
||||||
|
let normalized = normalize_path(path, &fs.cwd);
|
||||||
|
|
||||||
|
let entry = by_path.get(&normalized).ok_or_else(not_found)?;
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
Entry::File(file) => Ok(file.content.clone()),
|
||||||
|
Entry::Directory(_) => Err(is_a_directory()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
read_to_string(&self.inner, path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exists(&self, path: &SystemPath) -> bool {
|
||||||
|
let by_path = self.inner.by_path.read().unwrap();
|
||||||
|
let normalized = normalize_path(path, &self.inner.cwd);
|
||||||
|
|
||||||
|
by_path.contains_key(&normalized)
|
||||||
|
}
|
||||||
|
|
||||||
/// Writes the files to the file system.
|
/// Writes the files to the file system.
|
||||||
///
|
///
|
||||||
/// The operation overrides existing files with the same normalized path.
|
/// The operation overrides existing files with the same normalized path.
|
||||||
|
@ -66,7 +128,7 @@ impl MemoryFileSystem {
|
||||||
/// Enclosing directories are automatically created if they don't exist.
|
/// Enclosing directories are automatically created if they don't exist.
|
||||||
pub fn write_files<P, C>(&self, files: impl IntoIterator<Item = (P, C)>) -> Result<()>
|
pub fn write_files<P, C>(&self, files: impl IntoIterator<Item = (P, C)>) -> Result<()>
|
||||||
where
|
where
|
||||||
P: AsRef<FileSystemPath>,
|
P: AsRef<SystemPath>,
|
||||||
C: ToString,
|
C: ToString,
|
||||||
{
|
{
|
||||||
for (path, content) in files {
|
for (path, content) in files {
|
||||||
|
@ -81,11 +143,7 @@ impl MemoryFileSystem {
|
||||||
/// The operation overrides the content for an existing file with the same normalized `path`.
|
/// The operation overrides the content for an existing file with the same normalized `path`.
|
||||||
///
|
///
|
||||||
/// Enclosing directories are automatically created if they don't exist.
|
/// Enclosing directories are automatically created if they don't exist.
|
||||||
pub fn write_file(
|
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
||||||
&self,
|
|
||||||
path: impl AsRef<FileSystemPath>,
|
|
||||||
content: impl ToString,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut by_path = self.inner.by_path.write().unwrap();
|
let mut by_path = self.inner.by_path.write().unwrap();
|
||||||
|
|
||||||
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
||||||
|
@ -95,9 +153,10 @@ impl MemoryFileSystem {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_file(&self, path: impl AsRef<FileSystemPath>) -> Result<()> {
|
pub fn remove_file(&self, path: impl AsRef<SystemPath>) -> Result<()> {
|
||||||
let mut by_path = self.inner.by_path.write().unwrap();
|
fn remove_file(fs: &MemoryFileSystem, path: &SystemPath) -> Result<()> {
|
||||||
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
let mut by_path = fs.inner.by_path.write().unwrap();
|
||||||
|
let normalized = normalize_path(path, &fs.inner.cwd);
|
||||||
|
|
||||||
match by_path.entry(normalized) {
|
match by_path.entry(normalized) {
|
||||||
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
|
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
|
||||||
|
@ -111,10 +170,13 @@ impl MemoryFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remove_file(self, path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the last modified timestamp of the file stored at `path` to now.
|
/// Sets the last modified timestamp of the file stored at `path` to now.
|
||||||
///
|
///
|
||||||
/// Creates a new file if the file at `path` doesn't exist.
|
/// Creates a new file if the file at `path` doesn't exist.
|
||||||
pub fn touch(&self, path: impl AsRef<FileSystemPath>) -> Result<()> {
|
pub fn touch(&self, path: impl AsRef<SystemPath>) -> Result<()> {
|
||||||
let mut by_path = self.inner.by_path.write().unwrap();
|
let mut by_path = self.inner.by_path.write().unwrap();
|
||||||
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
||||||
|
|
||||||
|
@ -124,7 +186,7 @@ impl MemoryFileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a directory at `path`. All enclosing directories are created if they don't exist.
|
/// Creates a directory at `path`. All enclosing directories are created if they don't exist.
|
||||||
pub fn create_directory_all(&self, path: impl AsRef<FileSystemPath>) -> Result<()> {
|
pub fn create_directory_all(&self, path: impl AsRef<SystemPath>) -> Result<()> {
|
||||||
let mut by_path = self.inner.by_path.write().unwrap();
|
let mut by_path = self.inner.by_path.write().unwrap();
|
||||||
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
||||||
|
|
||||||
|
@ -137,9 +199,10 @@ impl MemoryFileSystem {
|
||||||
/// * If the directory is not empty
|
/// * If the directory is not empty
|
||||||
/// * The `path` is not a directory
|
/// * The `path` is not a directory
|
||||||
/// * The `path` does not exist
|
/// * The `path` does not exist
|
||||||
pub fn remove_directory(&self, path: impl AsRef<FileSystemPath>) -> Result<()> {
|
pub fn remove_directory(&self, path: impl AsRef<SystemPath>) -> Result<()> {
|
||||||
let mut by_path = self.inner.by_path.write().unwrap();
|
fn remove_directory(fs: &MemoryFileSystem, path: &SystemPath) -> Result<()> {
|
||||||
let normalized = normalize_path(path.as_ref(), &self.inner.cwd);
|
let mut by_path = fs.inner.by_path.write().unwrap();
|
||||||
|
let normalized = normalize_path(path, &fs.inner.cwd);
|
||||||
|
|
||||||
// Test if the directory is empty
|
// Test if the directory is empty
|
||||||
// Skip the directory path itself
|
// Skip the directory path itself
|
||||||
|
@ -162,48 +225,8 @@ impl MemoryFileSystem {
|
||||||
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
|
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl FileSystem for MemoryFileSystem {
|
remove_directory(self, path.as_ref())
|
||||||
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata> {
|
|
||||||
let by_path = self.inner.by_path.read().unwrap();
|
|
||||||
let normalized = normalize_path(path, &self.inner.cwd);
|
|
||||||
|
|
||||||
let entry = by_path.get(&normalized).ok_or_else(not_found)?;
|
|
||||||
|
|
||||||
let metadata = match entry {
|
|
||||||
Entry::File(file) => Metadata {
|
|
||||||
revision: file.last_modified.into(),
|
|
||||||
permissions: Some(Self::PERMISSION),
|
|
||||||
file_type: FileType::File,
|
|
||||||
},
|
|
||||||
Entry::Directory(directory) => Metadata {
|
|
||||||
revision: directory.last_modified.into(),
|
|
||||||
permissions: Some(Self::PERMISSION),
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&self, path: &FileSystemPath) -> Result<String> {
|
|
||||||
let by_path = self.inner.by_path.read().unwrap();
|
|
||||||
let normalized = normalize_path(path, &self.inner.cwd);
|
|
||||||
|
|
||||||
let entry = by_path.get(&normalized).ok_or_else(not_found)?;
|
|
||||||
|
|
||||||
match entry {
|
|
||||||
Entry::File(file) => Ok(file.content.clone()),
|
|
||||||
Entry::Directory(_) => Err(is_a_directory()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exists(&self, path: &FileSystemPath) -> bool {
|
|
||||||
let by_path = self.inner.by_path.read().unwrap();
|
|
||||||
let normalized = normalize_path(path, &self.inner.cwd);
|
|
||||||
|
|
||||||
by_path.contains_key(&normalized)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +295,7 @@ fn directory_not_empty() -> std::io::Error {
|
||||||
/// Normalizes the path by removing `.` and `..` components and transform the path into an absolute path.
|
/// Normalizes the path by removing `.` and `..` components and transform the path into an absolute path.
|
||||||
///
|
///
|
||||||
/// Adapted from https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
|
/// Adapted from https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
|
||||||
fn normalize_path(path: &FileSystemPath, cwd: &Utf8Path) -> Utf8PathBuf {
|
fn normalize_path(path: &SystemPath, cwd: &Utf8Path) -> Utf8PathBuf {
|
||||||
let path = camino::Utf8Path::new(path.as_str());
|
let path = camino::Utf8Path::new(path.as_str());
|
||||||
|
|
||||||
let mut components = path.components().peekable();
|
let mut components = path.components().peekable();
|
||||||
|
@ -353,14 +376,14 @@ mod tests {
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::file_system::{FileSystem, FileSystemPath, MemoryFileSystem, Result};
|
use crate::system::{MemoryFileSystem, Result, SystemPath};
|
||||||
|
|
||||||
/// Creates a file system with the given files.
|
/// Creates a file system with the given files.
|
||||||
///
|
///
|
||||||
/// The content of all files will be empty.
|
/// The content of all files will be empty.
|
||||||
fn with_files<P>(files: impl IntoIterator<Item = P>) -> super::MemoryFileSystem
|
fn with_files<P>(files: impl IntoIterator<Item = P>) -> super::MemoryFileSystem
|
||||||
where
|
where
|
||||||
P: AsRef<FileSystemPath>,
|
P: AsRef<SystemPath>,
|
||||||
{
|
{
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
fs.write_files(files.into_iter().map(|path| (path, "")))
|
fs.write_files(files.into_iter().map(|path| (path, "")))
|
||||||
|
@ -371,7 +394,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_file() {
|
fn is_file() {
|
||||||
let path = FileSystemPath::new("a.py");
|
let path = SystemPath::new("a.py");
|
||||||
let fs = with_files([path]);
|
let fs = with_files([path]);
|
||||||
|
|
||||||
assert!(fs.is_file(path));
|
assert!(fs.is_file(path));
|
||||||
|
@ -382,26 +405,26 @@ mod tests {
|
||||||
fn exists() {
|
fn exists() {
|
||||||
let fs = with_files(["a.py"]);
|
let fs = with_files(["a.py"]);
|
||||||
|
|
||||||
assert!(fs.exists(FileSystemPath::new("a.py")));
|
assert!(fs.exists(SystemPath::new("a.py")));
|
||||||
assert!(!fs.exists(FileSystemPath::new("b.py")));
|
assert!(!fs.exists(SystemPath::new("b.py")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exists_directories() {
|
fn exists_directories() {
|
||||||
let fs = with_files(["a/b/c.py"]);
|
let fs = with_files(["a/b/c.py"]);
|
||||||
|
|
||||||
assert!(fs.exists(FileSystemPath::new("a")));
|
assert!(fs.exists(SystemPath::new("a")));
|
||||||
assert!(fs.exists(FileSystemPath::new("a/b")));
|
assert!(fs.exists(SystemPath::new("a/b")));
|
||||||
assert!(fs.exists(FileSystemPath::new("a/b/c.py")));
|
assert!(fs.exists(SystemPath::new("a/b/c.py")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn path_normalization() {
|
fn path_normalization() {
|
||||||
let fs = with_files(["a.py"]);
|
let fs = with_files(["a.py"]);
|
||||||
|
|
||||||
assert!(fs.exists(FileSystemPath::new("a.py")));
|
assert!(fs.exists(SystemPath::new("a.py")));
|
||||||
assert!(fs.exists(FileSystemPath::new("/a.py")));
|
assert!(fs.exists(SystemPath::new("/a.py")));
|
||||||
assert!(fs.exists(FileSystemPath::new("/b/./../a.py")));
|
assert!(fs.exists(SystemPath::new("/b/./../a.py")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -410,7 +433,7 @@ mod tests {
|
||||||
|
|
||||||
// The default permissions match the default on Linux: 0755
|
// The default permissions match the default on Linux: 0755
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fs.metadata(FileSystemPath::new("a.py"))?.permissions(),
|
fs.metadata(SystemPath::new("a.py"))?.permissions(),
|
||||||
Some(MemoryFileSystem::PERMISSION)
|
Some(MemoryFileSystem::PERMISSION)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -420,7 +443,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn touch() -> Result<()> {
|
fn touch() -> Result<()> {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
let path = FileSystemPath::new("a.py");
|
let path = SystemPath::new("a.py");
|
||||||
|
|
||||||
// Creates a file if it doesn't exist
|
// Creates a file if it doesn't exist
|
||||||
fs.touch(path)?;
|
fs.touch(path)?;
|
||||||
|
@ -445,16 +468,14 @@ mod tests {
|
||||||
fn create_dir_all() {
|
fn create_dir_all() {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
|
|
||||||
fs.create_directory_all(FileSystemPath::new("a/b/c"))
|
fs.create_directory_all(SystemPath::new("a/b/c")).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(fs.is_directory(FileSystemPath::new("a")));
|
assert!(fs.is_directory(SystemPath::new("a")));
|
||||||
assert!(fs.is_directory(FileSystemPath::new("a/b")));
|
assert!(fs.is_directory(SystemPath::new("a/b")));
|
||||||
assert!(fs.is_directory(FileSystemPath::new("a/b/c")));
|
assert!(fs.is_directory(SystemPath::new("a/b/c")));
|
||||||
|
|
||||||
// Should not fail if the directory already exists
|
// Should not fail if the directory already exists
|
||||||
fs.create_directory_all(FileSystemPath::new("a/b/c"))
|
fs.create_directory_all(SystemPath::new("a/b/c")).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -462,7 +483,7 @@ mod tests {
|
||||||
let fs = with_files(["a/b.py"]);
|
let fs = with_files(["a/b.py"]);
|
||||||
|
|
||||||
let error = fs
|
let error = fs
|
||||||
.create_directory_all(FileSystemPath::new("a/b.py/c"))
|
.create_directory_all(SystemPath::new("a/b.py/c"))
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(error.kind(), ErrorKind::Other);
|
assert_eq!(error.kind(), ErrorKind::Other);
|
||||||
}
|
}
|
||||||
|
@ -472,7 +493,7 @@ mod tests {
|
||||||
let fs = with_files(["a/b.py"]);
|
let fs = with_files(["a/b.py"]);
|
||||||
|
|
||||||
let error = fs
|
let error = fs
|
||||||
.write_file(FileSystemPath::new("a/b.py/c"), "content".to_string())
|
.write_file(SystemPath::new("a/b.py/c"), "content".to_string())
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.kind(), ErrorKind::Other);
|
assert_eq!(error.kind(), ErrorKind::Other);
|
||||||
|
@ -485,7 +506,7 @@ mod tests {
|
||||||
fs.create_directory_all("a")?;
|
fs.create_directory_all("a")?;
|
||||||
|
|
||||||
let error = fs
|
let error = fs
|
||||||
.write_file(FileSystemPath::new("a"), "content".to_string())
|
.write_file(SystemPath::new("a"), "content".to_string())
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.kind(), ErrorKind::Other);
|
assert_eq!(error.kind(), ErrorKind::Other);
|
||||||
|
@ -496,11 +517,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn read() -> Result<()> {
|
fn read() -> Result<()> {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
let path = FileSystemPath::new("a.py");
|
let path = SystemPath::new("a.py");
|
||||||
|
|
||||||
fs.write_file(path, "Test content".to_string())?;
|
fs.write_file(path, "Test content".to_string())?;
|
||||||
|
|
||||||
assert_eq!(fs.read(path)?, "Test content");
|
assert_eq!(fs.read_to_string(path)?, "Test content");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -511,7 +532,7 @@ mod tests {
|
||||||
|
|
||||||
fs.create_directory_all("a")?;
|
fs.create_directory_all("a")?;
|
||||||
|
|
||||||
let error = fs.read(FileSystemPath::new("a")).unwrap_err();
|
let error = fs.read_to_string(SystemPath::new("a")).unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.kind(), ErrorKind::Other);
|
assert_eq!(error.kind(), ErrorKind::Other);
|
||||||
|
|
||||||
|
@ -522,7 +543,7 @@ mod tests {
|
||||||
fn read_fails_if_path_doesnt_exist() -> Result<()> {
|
fn read_fails_if_path_doesnt_exist() -> Result<()> {
|
||||||
let fs = MemoryFileSystem::new();
|
let fs = MemoryFileSystem::new();
|
||||||
|
|
||||||
let error = fs.read(FileSystemPath::new("a")).unwrap_err();
|
let error = fs.read_to_string(SystemPath::new("a")).unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.kind(), ErrorKind::NotFound);
|
assert_eq!(error.kind(), ErrorKind::NotFound);
|
||||||
|
|
||||||
|
@ -535,13 +556,13 @@ mod tests {
|
||||||
|
|
||||||
fs.remove_file("a/a.py")?;
|
fs.remove_file("a/a.py")?;
|
||||||
|
|
||||||
assert!(!fs.exists(FileSystemPath::new("a/a.py")));
|
assert!(!fs.exists(SystemPath::new("a/a.py")));
|
||||||
|
|
||||||
// It doesn't delete the enclosing directories
|
// It doesn't delete the enclosing directories
|
||||||
assert!(fs.exists(FileSystemPath::new("a")));
|
assert!(fs.exists(SystemPath::new("a")));
|
||||||
|
|
||||||
// It doesn't delete unrelated files.
|
// It doesn't delete unrelated files.
|
||||||
assert!(fs.exists(FileSystemPath::new("b.py")));
|
assert!(fs.exists(SystemPath::new("b.py")));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -573,10 +594,10 @@ mod tests {
|
||||||
|
|
||||||
fs.remove_directory("a")?;
|
fs.remove_directory("a")?;
|
||||||
|
|
||||||
assert!(!fs.exists(FileSystemPath::new("a")));
|
assert!(!fs.exists(SystemPath::new("a")));
|
||||||
|
|
||||||
// It doesn't delete unrelated files.
|
// It doesn't delete unrelated files.
|
||||||
assert!(fs.exists(FileSystemPath::new("b.py")));
|
assert!(fs.exists(SystemPath::new("b.py")));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -596,9 +617,9 @@ mod tests {
|
||||||
|
|
||||||
fs.remove_directory("foo").unwrap();
|
fs.remove_directory("foo").unwrap();
|
||||||
|
|
||||||
assert!(!fs.exists(FileSystemPath::new("foo")));
|
assert!(!fs.exists(SystemPath::new("foo")));
|
||||||
assert!(fs.exists(FileSystemPath::new("foo_bar.py")));
|
assert!(fs.exists(SystemPath::new("foo_bar.py")));
|
||||||
assert!(fs.exists(FileSystemPath::new("foob.py")));
|
assert!(fs.exists(SystemPath::new("foob.py")));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
use crate::file_system::{FileSystem, FileSystemPath, FileType, Metadata, Result};
|
use crate::system::{FileType, Metadata, Result, System, SystemPath};
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct OsFileSystem;
|
pub struct OsSystem;
|
||||||
|
|
||||||
impl OsFileSystem {
|
impl OsSystem {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn permissions(metadata: &std::fs::Metadata) -> Option<u32> {
|
fn permissions(metadata: &std::fs::Metadata) -> Option<u32> {
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
@ -23,8 +24,8 @@ impl OsFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystem for OsFileSystem {
|
impl System for OsSystem {
|
||||||
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata> {
|
fn path_metadata(&self, path: &SystemPath) -> Result<Metadata> {
|
||||||
let metadata = path.as_std_path().metadata()?;
|
let metadata = path.as_std_path().metadata()?;
|
||||||
let last_modified = FileTime::from_last_modification_time(&metadata);
|
let last_modified = FileTime::from_last_modification_time(&metadata);
|
||||||
|
|
||||||
|
@ -35,13 +36,17 @@ impl FileSystem for OsFileSystem {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&self, path: &FileSystemPath) -> Result<String> {
|
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||||
std::fs::read_to_string(path)
|
std::fs::read_to_string(path.as_std_path())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exists(&self, path: &FileSystemPath) -> bool {
|
fn path_exists(&self, path: &SystemPath) -> bool {
|
||||||
path.as_std_path().exists()
|
path.as_std_path().exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::fs::FileType> for FileType {
|
impl From<std::fs::FileType> for FileType {
|
|
@ -1,66 +1,25 @@
|
||||||
|
// TODO support untitled files for the LSP use case. Wrap a `str` and `String`
|
||||||
|
// The main question is how `as_std_path` would work for untitled files, that can only exist in the LSP case
|
||||||
|
// but there's no compile time guarantee that a [`OsSystem`] never gets an untitled file path.
|
||||||
|
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::{Path, StripPrefixError};
|
use std::path::{Path, StripPrefixError};
|
||||||
|
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
/// A slice of a path on [`System`](super::System) (akin to [`str`]).
|
||||||
|
|
||||||
use crate::file_revision::FileRevision;
|
|
||||||
pub use memory::MemoryFileSystem;
|
|
||||||
pub use os::OsFileSystem;
|
|
||||||
|
|
||||||
mod memory;
|
|
||||||
mod os;
|
|
||||||
|
|
||||||
pub type Result<T> = std::io::Result<T>;
|
|
||||||
|
|
||||||
/// An abstraction over `std::fs` with features tailored to Ruff's needs.
|
|
||||||
///
|
|
||||||
/// Provides a file system agnostic API to interact with files and directories.
|
|
||||||
/// Abstracting the file system operations enables:
|
|
||||||
///
|
|
||||||
/// * Accessing unsaved or even untitled files in the LSP use case
|
|
||||||
/// * Testing with an in-memory file system
|
|
||||||
/// * Running Ruff in a WASM environment without needing to stub out the full `std::fs` API.
|
|
||||||
pub trait FileSystem: std::fmt::Debug {
|
|
||||||
/// Reads the metadata of the file or directory at `path`.
|
|
||||||
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata>;
|
|
||||||
|
|
||||||
/// Reads the content of the file at `path`.
|
|
||||||
fn read(&self, path: &FileSystemPath) -> Result<String>;
|
|
||||||
|
|
||||||
/// Returns `true` if `path` exists.
|
|
||||||
fn exists(&self, path: &FileSystemPath) -> bool;
|
|
||||||
|
|
||||||
/// Returns `true` if `path` exists and is a directory.
|
|
||||||
fn is_directory(&self, path: &FileSystemPath) -> bool {
|
|
||||||
self.metadata(path)
|
|
||||||
.map_or(false, |metadata| metadata.file_type.is_directory())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if `path` exists and is a file.
|
|
||||||
fn is_file(&self, path: &FileSystemPath) -> bool {
|
|
||||||
self.metadata(path)
|
|
||||||
.map_or(false, |metadata| metadata.file_type.is_file())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO support untitled files for the LSP use case. Wrap a `str` and `String`
|
|
||||||
// The main question is how `as_std_path` would work for untitled files, that can only exist in the LSP case
|
|
||||||
// but there's no compile time guarantee that a [`OsFileSystem`] never gets an untitled file path.
|
|
||||||
|
|
||||||
/// Path to a file or directory stored in [`FileSystem`].
|
|
||||||
///
|
///
|
||||||
/// The path is guaranteed to be valid UTF-8.
|
/// The path is guaranteed to be valid UTF-8.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
pub struct FileSystemPath(Utf8Path);
|
pub struct SystemPath(Utf8Path);
|
||||||
|
|
||||||
impl FileSystemPath {
|
impl SystemPath {
|
||||||
pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
|
pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
// SAFETY: FsPath is marked as #[repr(transparent)] so the conversion from a
|
// SAFETY: FsPath is marked as #[repr(transparent)] so the conversion from a
|
||||||
// *const Utf8Path to a *const FsPath is valid.
|
// *const Utf8Path to a *const FsPath is valid.
|
||||||
unsafe { &*(path as *const Utf8Path as *const FileSystemPath) }
|
unsafe { &*(path as *const Utf8Path as *const SystemPath) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the file extension, if possible.
|
/// Extracts the file extension, if possible.
|
||||||
|
@ -75,10 +34,10 @@ impl FileSystemPath {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// assert_eq!("rs", FileSystemPath::new("foo.rs").extension().unwrap());
|
/// assert_eq!("rs", SystemPath::new("foo.rs").extension().unwrap());
|
||||||
/// assert_eq!("gz", FileSystemPath::new("foo.tar.gz").extension().unwrap());
|
/// assert_eq!("gz", SystemPath::new("foo.tar.gz").extension().unwrap());
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See [`Path::extension`] for more details.
|
/// See [`Path::extension`] for more details.
|
||||||
|
@ -95,9 +54,9 @@ impl FileSystemPath {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// let path = FileSystemPath::new("/etc/passwd");
|
/// let path = SystemPath::new("/etc/passwd");
|
||||||
///
|
///
|
||||||
/// assert!(path.starts_with("/etc"));
|
/// assert!(path.starts_with("/etc"));
|
||||||
/// assert!(path.starts_with("/etc/"));
|
/// assert!(path.starts_with("/etc/"));
|
||||||
|
@ -108,11 +67,11 @@ impl FileSystemPath {
|
||||||
/// assert!(!path.starts_with("/e"));
|
/// assert!(!path.starts_with("/e"));
|
||||||
/// assert!(!path.starts_with("/etc/passwd.txt"));
|
/// assert!(!path.starts_with("/etc/passwd.txt"));
|
||||||
///
|
///
|
||||||
/// assert!(!FileSystemPath::new("/etc/foo.rs").starts_with("/etc/foo"));
|
/// assert!(!SystemPath::new("/etc/foo.rs").starts_with("/etc/foo"));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn starts_with(&self, base: impl AsRef<FileSystemPath>) -> bool {
|
pub fn starts_with(&self, base: impl AsRef<SystemPath>) -> bool {
|
||||||
self.0.starts_with(base.as_ref())
|
self.0.starts_with(base.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,9 +82,9 @@ impl FileSystemPath {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// let path = FileSystemPath::new("/etc/resolv.conf");
|
/// let path = SystemPath::new("/etc/resolv.conf");
|
||||||
///
|
///
|
||||||
/// assert!(path.ends_with("resolv.conf"));
|
/// assert!(path.ends_with("resolv.conf"));
|
||||||
/// assert!(path.ends_with("etc/resolv.conf"));
|
/// assert!(path.ends_with("etc/resolv.conf"));
|
||||||
|
@ -136,7 +95,7 @@ impl FileSystemPath {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn ends_with(&self, child: impl AsRef<FileSystemPath>) -> bool {
|
pub fn ends_with(&self, child: impl AsRef<SystemPath>) -> bool {
|
||||||
self.0.ends_with(child.as_ref())
|
self.0.ends_with(child.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,20 +106,20 @@ impl FileSystemPath {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// let path = FileSystemPath::new("/foo/bar");
|
/// let path = SystemPath::new("/foo/bar");
|
||||||
/// let parent = path.parent().unwrap();
|
/// let parent = path.parent().unwrap();
|
||||||
/// assert_eq!(parent, FileSystemPath::new("/foo"));
|
/// assert_eq!(parent, SystemPath::new("/foo"));
|
||||||
///
|
///
|
||||||
/// let grand_parent = parent.parent().unwrap();
|
/// let grand_parent = parent.parent().unwrap();
|
||||||
/// assert_eq!(grand_parent, FileSystemPath::new("/"));
|
/// assert_eq!(grand_parent, SystemPath::new("/"));
|
||||||
/// assert_eq!(grand_parent.parent(), None);
|
/// assert_eq!(grand_parent.parent(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn parent(&self) -> Option<&FileSystemPath> {
|
pub fn parent(&self) -> Option<&SystemPath> {
|
||||||
self.0.parent().map(FileSystemPath::new)
|
self.0.parent().map(SystemPath::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces an iterator over the [`camino::Utf8Component`]s of the path.
|
/// Produces an iterator over the [`camino::Utf8Component`]s of the path.
|
||||||
|
@ -185,9 +144,9 @@ impl FileSystemPath {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use camino::{Utf8Component};
|
/// use camino::{Utf8Component};
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// let mut components = FileSystemPath::new("/tmp/foo.txt").components();
|
/// let mut components = SystemPath::new("/tmp/foo.txt").components();
|
||||||
///
|
///
|
||||||
/// assert_eq!(components.next(), Some(Utf8Component::RootDir));
|
/// assert_eq!(components.next(), Some(Utf8Component::RootDir));
|
||||||
/// assert_eq!(components.next(), Some(Utf8Component::Normal("tmp")));
|
/// assert_eq!(components.next(), Some(Utf8Component::Normal("tmp")));
|
||||||
|
@ -212,14 +171,14 @@ impl FileSystemPath {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use camino::Utf8Path;
|
/// use camino::Utf8Path;
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// assert_eq!(Some("bin"), FileSystemPath::new("/usr/bin/").file_name());
|
/// assert_eq!(Some("bin"), SystemPath::new("/usr/bin/").file_name());
|
||||||
/// assert_eq!(Some("foo.txt"), FileSystemPath::new("tmp/foo.txt").file_name());
|
/// assert_eq!(Some("foo.txt"), SystemPath::new("tmp/foo.txt").file_name());
|
||||||
/// assert_eq!(Some("foo.txt"), FileSystemPath::new("foo.txt/.").file_name());
|
/// assert_eq!(Some("foo.txt"), SystemPath::new("foo.txt/.").file_name());
|
||||||
/// assert_eq!(Some("foo.txt"), FileSystemPath::new("foo.txt/.//").file_name());
|
/// assert_eq!(Some("foo.txt"), SystemPath::new("foo.txt/.//").file_name());
|
||||||
/// assert_eq!(None, FileSystemPath::new("foo.txt/..").file_name());
|
/// assert_eq!(None, SystemPath::new("foo.txt/..").file_name());
|
||||||
/// assert_eq!(None, FileSystemPath::new("/").file_name());
|
/// assert_eq!(None, SystemPath::new("/").file_name());
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -229,7 +188,7 @@ impl FileSystemPath {
|
||||||
|
|
||||||
/// Extracts the stem (non-extension) portion of [`self.file_name`].
|
/// Extracts the stem (non-extension) portion of [`self.file_name`].
|
||||||
///
|
///
|
||||||
/// [`self.file_name`]: FileSystemPath::file_name
|
/// [`self.file_name`]: SystemPath::file_name
|
||||||
///
|
///
|
||||||
/// The stem is:
|
/// The stem is:
|
||||||
///
|
///
|
||||||
|
@ -241,10 +200,10 @@ impl FileSystemPath {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::FileSystemPath;
|
/// use ruff_db::system::SystemPath;
|
||||||
///
|
///
|
||||||
/// assert_eq!("foo", FileSystemPath::new("foo.rs").file_stem().unwrap());
|
/// assert_eq!("foo", SystemPath::new("foo.rs").file_stem().unwrap());
|
||||||
/// assert_eq!("foo.tar", FileSystemPath::new("foo.tar.gz").file_stem().unwrap());
|
/// assert_eq!("foo.tar", SystemPath::new("foo.tar.gz").file_stem().unwrap());
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -259,77 +218,77 @@ impl FileSystemPath {
|
||||||
/// If `base` is not a prefix of `self` (i.e., [`starts_with`]
|
/// If `base` is not a prefix of `self` (i.e., [`starts_with`]
|
||||||
/// returns `false`), returns [`Err`].
|
/// returns `false`), returns [`Err`].
|
||||||
///
|
///
|
||||||
/// [`starts_with`]: FileSystemPath::starts_with
|
/// [`starts_with`]: SystemPath::starts_with
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::{FileSystemPath, FileSystemPathBuf};
|
/// use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
///
|
///
|
||||||
/// let path = FileSystemPath::new("/test/haha/foo.txt");
|
/// let path = SystemPath::new("/test/haha/foo.txt");
|
||||||
///
|
///
|
||||||
/// assert_eq!(path.strip_prefix("/"), Ok(FileSystemPath::new("test/haha/foo.txt")));
|
/// assert_eq!(path.strip_prefix("/"), Ok(SystemPath::new("test/haha/foo.txt")));
|
||||||
/// assert_eq!(path.strip_prefix("/test"), Ok(FileSystemPath::new("haha/foo.txt")));
|
/// assert_eq!(path.strip_prefix("/test"), Ok(SystemPath::new("haha/foo.txt")));
|
||||||
/// assert_eq!(path.strip_prefix("/test/"), Ok(FileSystemPath::new("haha/foo.txt")));
|
/// assert_eq!(path.strip_prefix("/test/"), Ok(SystemPath::new("haha/foo.txt")));
|
||||||
/// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(FileSystemPath::new("")));
|
/// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(SystemPath::new("")));
|
||||||
/// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(FileSystemPath::new("")));
|
/// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(SystemPath::new("")));
|
||||||
///
|
///
|
||||||
/// assert!(path.strip_prefix("test").is_err());
|
/// assert!(path.strip_prefix("test").is_err());
|
||||||
/// assert!(path.strip_prefix("/haha").is_err());
|
/// assert!(path.strip_prefix("/haha").is_err());
|
||||||
///
|
///
|
||||||
/// let prefix = FileSystemPathBuf::from("/test/");
|
/// let prefix = SystemPathBuf::from("/test/");
|
||||||
/// assert_eq!(path.strip_prefix(prefix), Ok(FileSystemPath::new("haha/foo.txt")));
|
/// assert_eq!(path.strip_prefix(prefix), Ok(SystemPath::new("haha/foo.txt")));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn strip_prefix(
|
pub fn strip_prefix(
|
||||||
&self,
|
&self,
|
||||||
base: impl AsRef<FileSystemPath>,
|
base: impl AsRef<SystemPath>,
|
||||||
) -> std::result::Result<&FileSystemPath, StripPrefixError> {
|
) -> std::result::Result<&SystemPath, StripPrefixError> {
|
||||||
self.0.strip_prefix(base.as_ref()).map(FileSystemPath::new)
|
self.0.strip_prefix(base.as_ref()).map(SystemPath::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an owned [`FileSystemPathBuf`] with `path` adjoined to `self`.
|
/// Creates an owned [`SystemPathBuf`] with `path` adjoined to `self`.
|
||||||
///
|
///
|
||||||
/// See [`std::path::PathBuf::push`] for more details on what it means to adjoin a path.
|
/// See [`std::path::PathBuf::push`] for more details on what it means to adjoin a path.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::{FileSystemPath, FileSystemPathBuf};
|
/// use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
///
|
///
|
||||||
/// assert_eq!(FileSystemPath::new("/etc").join("passwd"), FileSystemPathBuf::from("/etc/passwd"));
|
/// assert_eq!(SystemPath::new("/etc").join("passwd"), SystemPathBuf::from("/etc/passwd"));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn join(&self, path: impl AsRef<FileSystemPath>) -> FileSystemPathBuf {
|
pub fn join(&self, path: impl AsRef<SystemPath>) -> SystemPathBuf {
|
||||||
FileSystemPathBuf::from_utf8_path_buf(self.0.join(&path.as_ref().0))
|
SystemPathBuf::from_utf8_path_buf(self.0.join(&path.as_ref().0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an owned [`FileSystemPathBuf`] like `self` but with the given extension.
|
/// Creates an owned [`SystemPathBuf`] like `self` but with the given extension.
|
||||||
///
|
///
|
||||||
/// See [`std::path::PathBuf::set_extension`] for more details.
|
/// See [`std::path::PathBuf::set_extension`] for more details.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::{FileSystemPath, FileSystemPathBuf};
|
/// use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
///
|
///
|
||||||
/// let path = FileSystemPath::new("foo.rs");
|
/// let path = SystemPath::new("foo.rs");
|
||||||
/// assert_eq!(path.with_extension("txt"), FileSystemPathBuf::from("foo.txt"));
|
/// assert_eq!(path.with_extension("txt"), SystemPathBuf::from("foo.txt"));
|
||||||
///
|
///
|
||||||
/// let path = FileSystemPath::new("foo.tar.gz");
|
/// let path = SystemPath::new("foo.tar.gz");
|
||||||
/// assert_eq!(path.with_extension(""), FileSystemPathBuf::from("foo.tar"));
|
/// assert_eq!(path.with_extension(""), SystemPathBuf::from("foo.tar"));
|
||||||
/// assert_eq!(path.with_extension("xz"), FileSystemPathBuf::from("foo.tar.xz"));
|
/// assert_eq!(path.with_extension("xz"), SystemPathBuf::from("foo.tar.xz"));
|
||||||
/// assert_eq!(path.with_extension("").with_extension("txt"), FileSystemPathBuf::from("foo.txt"));
|
/// assert_eq!(path.with_extension("").with_extension("txt"), SystemPathBuf::from("foo.txt"));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_extension(&self, extension: &str) -> FileSystemPathBuf {
|
pub fn with_extension(&self, extension: &str) -> SystemPathBuf {
|
||||||
FileSystemPathBuf::from_utf8_path_buf(self.0.with_extension(extension))
|
SystemPathBuf::from_utf8_path_buf(self.0.with_extension(extension))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the path to an owned [`FileSystemPathBuf`].
|
/// Converts the path to an owned [`SystemPathBuf`].
|
||||||
pub fn to_path_buf(&self) -> FileSystemPathBuf {
|
pub fn to_path_buf(&self) -> SystemPathBuf {
|
||||||
FileSystemPathBuf(self.0.to_path_buf())
|
SystemPathBuf(self.0.to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the path as a string slice.
|
/// Returns the path as a string slice.
|
||||||
|
@ -344,19 +303,19 @@ impl FileSystemPath {
|
||||||
self.0.as_std_path()
|
self.0.as_std_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_std_path(path: &Path) -> Option<&FileSystemPath> {
|
pub fn from_std_path(path: &Path) -> Option<&SystemPath> {
|
||||||
Some(FileSystemPath::new(Utf8Path::from_path(path)?))
|
Some(SystemPath::new(Utf8Path::from_path(path)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Owned path to a file or directory stored in [`FileSystem`].
|
/// An owned, mutable path on [`System`](`super::System`) (akin to [`String`]).
|
||||||
///
|
///
|
||||||
/// The path is guaranteed to be valid UTF-8.
|
/// The path is guaranteed to be valid UTF-8.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
|
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
|
||||||
pub struct FileSystemPathBuf(Utf8PathBuf);
|
pub struct SystemPathBuf(Utf8PathBuf);
|
||||||
|
|
||||||
impl FileSystemPathBuf {
|
impl SystemPathBuf {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Utf8PathBuf::new())
|
Self(Utf8PathBuf::new())
|
||||||
}
|
}
|
||||||
|
@ -386,82 +345,82 @@ impl FileSystemPathBuf {
|
||||||
/// Pushing a relative path extends the existing path:
|
/// Pushing a relative path extends the existing path:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use ruff_db::file_system::FileSystemPathBuf;
|
/// use ruff_db::system::SystemPathBuf;
|
||||||
///
|
///
|
||||||
/// let mut path = FileSystemPathBuf::from("/tmp");
|
/// let mut path = SystemPathBuf::from("/tmp");
|
||||||
/// path.push("file.bk");
|
/// path.push("file.bk");
|
||||||
/// assert_eq!(path, FileSystemPathBuf::from("/tmp/file.bk"));
|
/// assert_eq!(path, SystemPathBuf::from("/tmp/file.bk"));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Pushing an absolute path replaces the existing path:
|
/// Pushing an absolute path replaces the existing path:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// use ruff_db::file_system::FileSystemPathBuf;
|
/// use ruff_db::system::SystemPathBuf;
|
||||||
///
|
///
|
||||||
/// let mut path = FileSystemPathBuf::from("/tmp");
|
/// let mut path = SystemPathBuf::from("/tmp");
|
||||||
/// path.push("/etc");
|
/// path.push("/etc");
|
||||||
/// assert_eq!(path, FileSystemPathBuf::from("/etc"));
|
/// assert_eq!(path, SystemPathBuf::from("/etc"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn push(&mut self, path: impl AsRef<FileSystemPath>) {
|
pub fn push(&mut self, path: impl AsRef<SystemPath>) {
|
||||||
self.0.push(&path.as_ref().0);
|
self.0.push(&path.as_ref().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_path(&self) -> &FileSystemPath {
|
pub fn as_path(&self) -> &SystemPath {
|
||||||
FileSystemPath::new(&self.0)
|
SystemPath::new(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for FileSystemPathBuf {
|
impl From<&str> for SystemPathBuf {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
FileSystemPathBuf::from_utf8_path_buf(Utf8PathBuf::from(value))
|
SystemPathBuf::from_utf8_path_buf(Utf8PathBuf::from(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FileSystemPathBuf {
|
impl Default for SystemPathBuf {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<FileSystemPath> for FileSystemPathBuf {
|
impl AsRef<SystemPath> for SystemPathBuf {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &FileSystemPath {
|
fn as_ref(&self) -> &SystemPath {
|
||||||
self.as_path()
|
self.as_path()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<FileSystemPath> for FileSystemPath {
|
impl AsRef<SystemPath> for SystemPath {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &FileSystemPath {
|
fn as_ref(&self) -> &SystemPath {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<FileSystemPath> for str {
|
impl AsRef<SystemPath> for str {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &FileSystemPath {
|
fn as_ref(&self) -> &SystemPath {
|
||||||
FileSystemPath::new(self)
|
SystemPath::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<FileSystemPath> for String {
|
impl AsRef<SystemPath> for String {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &FileSystemPath {
|
fn as_ref(&self) -> &SystemPath {
|
||||||
FileSystemPath::new(self)
|
SystemPath::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Path> for FileSystemPath {
|
impl AsRef<Path> for SystemPath {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_ref(&self) -> &Path {
|
fn as_ref(&self) -> &Path {
|
||||||
self.0.as_std_path()
|
self.0.as_std_path()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for FileSystemPathBuf {
|
impl Deref for SystemPathBuf {
|
||||||
type Target = FileSystemPath;
|
type Target = SystemPath;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -469,68 +428,26 @@ impl Deref for FileSystemPathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for FileSystemPath {
|
impl std::fmt::Debug for SystemPath {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for FileSystemPath {
|
impl std::fmt::Display for SystemPath {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for FileSystemPathBuf {
|
impl std::fmt::Debug for SystemPathBuf {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for FileSystemPathBuf {
|
impl std::fmt::Display for SystemPathBuf {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct Metadata {
|
|
||||||
revision: FileRevision,
|
|
||||||
permissions: Option<u32>,
|
|
||||||
file_type: FileType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Metadata {
|
|
||||||
pub fn revision(&self) -> FileRevision {
|
|
||||||
self.revision
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn permissions(&self) -> Option<u32> {
|
|
||||||
self.permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_type(&self) -> FileType {
|
|
||||||
self.file_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
|
||||||
pub enum FileType {
|
|
||||||
File,
|
|
||||||
Directory,
|
|
||||||
Symlink,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileType {
|
|
||||||
pub const fn is_file(self) -> bool {
|
|
||||||
matches!(self, FileType::File)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn is_directory(self) -> bool {
|
|
||||||
matches!(self, FileType::Directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn is_symlink(self) -> bool {
|
|
||||||
matches!(self, FileType::Symlink)
|
|
||||||
}
|
|
||||||
}
|
|
167
crates/ruff_db/src/system/test.rs
Normal file
167
crates/ruff_db/src/system/test.rs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
use crate::files::{File, FilePath};
|
||||||
|
use crate::system::{MemoryFileSystem, Metadata, OsSystem, System, SystemPath};
|
||||||
|
use crate::Db;
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
/// System implementation intended for testing.
|
||||||
|
///
|
||||||
|
/// It uses a memory-file system by default, but can be switched to the real file system for tests
|
||||||
|
/// verifying more advanced file system features.
|
||||||
|
///
|
||||||
|
/// ## Warning
|
||||||
|
/// Don't use this system for production code. It's intended for testing only.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct TestSystem {
|
||||||
|
inner: TestFileSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestSystem {
|
||||||
|
pub fn snapshot(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.snapshot(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the memory file system.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
/// If this test db isn't using a memory file system.
|
||||||
|
pub fn memory_file_system(&self) -> &MemoryFileSystem {
|
||||||
|
if let TestFileSystem::Stub(fs) = &self.inner {
|
||||||
|
fs
|
||||||
|
} else {
|
||||||
|
panic!("The test db is not using a memory file system");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_os_system(&mut self) {
|
||||||
|
self.inner = TestFileSystem::Os(OsSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System for TestSystem {
|
||||||
|
fn path_metadata(&self, path: &SystemPath) -> crate::system::Result<Metadata> {
|
||||||
|
match &self.inner {
|
||||||
|
TestFileSystem::Stub(fs) => fs.metadata(path),
|
||||||
|
TestFileSystem::Os(fs) => fs.path_metadata(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_to_string(&self, path: &SystemPath) -> crate::system::Result<String> {
|
||||||
|
match &self.inner {
|
||||||
|
TestFileSystem::Stub(fs) => fs.read_to_string(path),
|
||||||
|
TestFileSystem::Os(fs) => fs.read_to_string(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_exists(&self, path: &SystemPath) -> bool {
|
||||||
|
match &self.inner {
|
||||||
|
TestFileSystem::Stub(fs) => fs.exists(path),
|
||||||
|
TestFileSystem::Os(fs) => fs.path_exists(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_directory(&self, path: &SystemPath) -> bool {
|
||||||
|
match &self.inner {
|
||||||
|
TestFileSystem::Stub(fs) => fs.is_directory(path),
|
||||||
|
TestFileSystem::Os(fs) => fs.is_directory(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file(&self, path: &SystemPath) -> bool {
|
||||||
|
match &self.inner {
|
||||||
|
TestFileSystem::Stub(fs) => fs.is_file(path),
|
||||||
|
TestFileSystem::Os(fs) => fs.is_file(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait for databases that use [`TestSystem`].
|
||||||
|
///
|
||||||
|
/// Provides various helper function that ease testing.
|
||||||
|
pub trait DbWithTestSystem: Db + Sized {
|
||||||
|
fn test_system(&self) -> &TestSystem;
|
||||||
|
|
||||||
|
fn test_system_mut(&mut self) -> &mut TestSystem;
|
||||||
|
|
||||||
|
/// Writes the content of the given file and notifies the Db about the change.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If the system isn't using the memory file system.
|
||||||
|
fn write_file(
|
||||||
|
&mut self,
|
||||||
|
path: impl AsRef<SystemPath>,
|
||||||
|
content: impl ToString,
|
||||||
|
) -> crate::system::Result<()> {
|
||||||
|
let path = path.as_ref().to_path_buf();
|
||||||
|
let result = self
|
||||||
|
.test_system()
|
||||||
|
.memory_file_system()
|
||||||
|
.write_file(&path, content);
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
File::touch_path(self, &FilePath::System(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the content of the given file and notifies the Db about the change.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If the system isn't using the memory file system for testing.
|
||||||
|
fn write_files<P, C, I>(&mut self, files: I) -> crate::system::Result<()>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (P, C)>,
|
||||||
|
P: AsRef<SystemPath>,
|
||||||
|
C: ToString,
|
||||||
|
{
|
||||||
|
for (path, content) in files {
|
||||||
|
self.write_file(path, content)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uses the real file system instead of the memory file system.
|
||||||
|
///
|
||||||
|
/// This useful for testing advanced file system features like permissions, symlinks, etc.
|
||||||
|
///
|
||||||
|
/// Note that any files written to the memory file system won't be copied over.
|
||||||
|
fn use_os_system(&mut self) {
|
||||||
|
self.test_system_mut().use_os_system();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the memory file system.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
/// If this system isn't using a memory file system.
|
||||||
|
fn memory_file_system(&self) -> &MemoryFileSystem {
|
||||||
|
self.test_system().memory_file_system()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum TestFileSystem {
|
||||||
|
Stub(MemoryFileSystem),
|
||||||
|
Os(OsSystem),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestFileSystem {
|
||||||
|
fn snapshot(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Stub(fs) => Self::Stub(fs.snapshot()),
|
||||||
|
Self::Os(fs) => Self::Os(fs.snapshot()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TestFileSystem {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Stub(MemoryFileSystem::default())
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,14 +2,15 @@ use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use zip::{read::ZipFile, ZipArchive};
|
use zip::{read::ZipFile, ZipArchive, ZipWriter};
|
||||||
|
|
||||||
use crate::file_revision::FileRevision;
|
use crate::file_revision::FileRevision;
|
||||||
pub use path::{VendoredPath, VendoredPathBuf};
|
|
||||||
|
|
||||||
pub mod path;
|
pub use self::path::{VendoredPath, VendoredPathBuf};
|
||||||
|
|
||||||
|
mod path;
|
||||||
|
|
||||||
type Result<T> = io::Result<T>;
|
type Result<T> = io::Result<T>;
|
||||||
type LockedZipArchive<'a> = MutexGuard<'a, VendoredZipArchive>;
|
type LockedZipArchive<'a> = MutexGuard<'a, VendoredZipArchive>;
|
||||||
|
@ -20,19 +21,34 @@ type LockedZipArchive<'a> = MutexGuard<'a, VendoredZipArchive>;
|
||||||
/// "Files" in the `VendoredFileSystem` are read-only and immutable.
|
/// "Files" in the `VendoredFileSystem` are read-only and immutable.
|
||||||
/// Directories are supported, but symlinks and hardlinks cannot exist.
|
/// Directories are supported, but symlinks and hardlinks cannot exist.
|
||||||
pub struct VendoredFileSystem {
|
pub struct VendoredFileSystem {
|
||||||
inner: Mutex<VendoredZipArchive>,
|
inner: Arc<Mutex<VendoredZipArchive>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VendoredFileSystem {
|
impl VendoredFileSystem {
|
||||||
pub fn new(raw_bytes: &'static [u8]) -> Result<Self> {
|
pub fn new_static(raw_bytes: &'static [u8]) -> Result<Self> {
|
||||||
|
Self::new_impl(Cow::Borrowed(raw_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(raw_bytes: Vec<u8>) -> Result<Self> {
|
||||||
|
Self::new_impl(Cow::Owned(raw_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_impl(data: Cow<'static, [u8]>) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: Mutex::new(VendoredZipArchive::new(raw_bytes)?),
|
inner: Arc::new(Mutex::new(VendoredZipArchive::new(data)?)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exists(&self, path: &VendoredPath) -> bool {
|
pub fn snapshot(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::clone(&self.inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exists(&self, path: impl AsRef<VendoredPath>) -> bool {
|
||||||
|
fn exists(fs: &VendoredFileSystem, path: &VendoredPath) -> bool {
|
||||||
let normalized = NormalizedVendoredPath::from(path);
|
let normalized = NormalizedVendoredPath::from(path);
|
||||||
let mut archive = self.lock_archive();
|
let mut archive = fs.lock_archive();
|
||||||
|
|
||||||
// Must probe the zipfile twice, as "stdlib" and "stdlib/" are considered
|
// Must probe the zipfile twice, as "stdlib" and "stdlib/" are considered
|
||||||
// different paths in a zip file, but we want to abstract over that difference here
|
// different paths in a zip file, but we want to abstract over that difference here
|
||||||
|
@ -44,22 +60,36 @@ impl VendoredFileSystem {
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metadata(&self, path: &VendoredPath) -> Option<Metadata> {
|
exists(self, path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn metadata(&self, path: impl AsRef<VendoredPath>) -> Result<Metadata> {
|
||||||
|
fn metadata(fs: &VendoredFileSystem, path: &VendoredPath) -> Result<Metadata> {
|
||||||
let normalized = NormalizedVendoredPath::from(path);
|
let normalized = NormalizedVendoredPath::from(path);
|
||||||
let mut archive = self.lock_archive();
|
let mut archive = fs.lock_archive();
|
||||||
|
|
||||||
// Must probe the zipfile twice, as "stdlib" and "stdlib/" are considered
|
// Must probe the zipfile twice, as "stdlib" and "stdlib/" are considered
|
||||||
// different paths in a zip file, but we want to abstract over that difference here
|
// different paths in a zip file, but we want to abstract over that difference here
|
||||||
// so that paths relative to the `VendoredFileSystem`
|
// so that paths relative to the `VendoredFileSystem`
|
||||||
// work the same as other paths in Ruff.
|
// work the same as other paths in Ruff.
|
||||||
if let Ok(zip_file) = archive.lookup_path(&normalized) {
|
if let Ok(zip_file) = archive.lookup_path(&normalized) {
|
||||||
return Some(Metadata::from_zip_file(zip_file));
|
return Ok(Metadata::from_zip_file(zip_file));
|
||||||
}
|
}
|
||||||
if let Ok(zip_file) = archive.lookup_path(&normalized.with_trailing_slash()) {
|
let zip_file = archive.lookup_path(&normalized.with_trailing_slash())?;
|
||||||
return Some(Metadata::from_zip_file(zip_file));
|
Ok(Metadata::from_zip_file(zip_file))
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
metadata(self, path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_directory(&self, path: impl AsRef<VendoredPath>) -> bool {
|
||||||
|
self.metadata(path)
|
||||||
|
.is_ok_and(|metadata| metadata.kind().is_directory())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_file(&self, path: impl AsRef<VendoredPath>) -> bool {
|
||||||
|
self.metadata(path)
|
||||||
|
.is_ok_and(|metadata| metadata.kind().is_file())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the entire contents of the zip file at `path` into a string
|
/// Read the entire contents of the zip file at `path` into a string
|
||||||
|
@ -68,14 +98,18 @@ impl VendoredFileSystem {
|
||||||
/// - The path does not exist in the underlying zip archive
|
/// - The path does not exist in the underlying zip archive
|
||||||
/// - The path exists in the underlying zip archive, but represents a directory
|
/// - The path exists in the underlying zip archive, but represents a directory
|
||||||
/// - The contents of the zip file at `path` contain invalid UTF-8
|
/// - The contents of the zip file at `path` contain invalid UTF-8
|
||||||
pub fn read(&self, path: &VendoredPath) -> Result<String> {
|
pub fn read_to_string(&self, path: impl AsRef<VendoredPath>) -> Result<String> {
|
||||||
let mut archive = self.lock_archive();
|
fn read_to_string(fs: &VendoredFileSystem, path: &VendoredPath) -> Result<String> {
|
||||||
|
let mut archive = fs.lock_archive();
|
||||||
let mut zip_file = archive.lookup_path(&NormalizedVendoredPath::from(path))?;
|
let mut zip_file = archive.lookup_path(&NormalizedVendoredPath::from(path))?;
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
zip_file.read_to_string(&mut buffer)?;
|
zip_file.read_to_string(&mut buffer)?;
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
read_to_string(self, path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
/// Acquire a lock on the underlying zip archive.
|
/// Acquire a lock on the underlying zip archive.
|
||||||
/// The call will block until it is able to acquire the lock.
|
/// The call will block until it is able to acquire the lock.
|
||||||
///
|
///
|
||||||
|
@ -112,6 +146,20 @@ impl fmt::Debug for VendoredFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for VendoredFileSystem {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
|
let mut cursor = io::Cursor::new(&mut bytes);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut writer = ZipWriter::new(&mut cursor);
|
||||||
|
writer.finish().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
VendoredFileSystem::new(bytes).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Private struct only used in `Debug` implementations
|
/// Private struct only used in `Debug` implementations
|
||||||
///
|
///
|
||||||
/// This could possibly be unified with the `Metadata` struct,
|
/// This could possibly be unified with the `Metadata` struct,
|
||||||
|
@ -195,10 +243,10 @@ impl Metadata {
|
||||||
|
|
||||||
/// Newtype wrapper around a ZipArchive.
|
/// Newtype wrapper around a ZipArchive.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct VendoredZipArchive(ZipArchive<io::Cursor<&'static [u8]>>);
|
struct VendoredZipArchive(ZipArchive<io::Cursor<Cow<'static, [u8]>>>);
|
||||||
|
|
||||||
impl VendoredZipArchive {
|
impl VendoredZipArchive {
|
||||||
fn new(data: &'static [u8]) -> Result<Self> {
|
fn new(data: Cow<'static, [u8]>) -> Result<Self> {
|
||||||
Ok(Self(ZipArchive::new(io::Cursor::new(data))?))
|
Ok(Self(ZipArchive::new(io::Cursor::new(data))?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,11 +338,11 @@ impl<'a> From<&'a VendoredPath> for NormalizedVendoredPath<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
use once_cell::sync::Lazy;
|
use zip::result::ZipResult;
|
||||||
use zip::write::FileOptions;
|
use zip::write::FileOptions;
|
||||||
use zip::{CompressionMethod, ZipWriter};
|
use zip::{CompressionMethod, ZipWriter};
|
||||||
|
|
||||||
|
@ -303,37 +351,66 @@ mod tests {
|
||||||
const FUNCTOOLS_CONTENTS: &str = "def update_wrapper(): ...";
|
const FUNCTOOLS_CONTENTS: &str = "def update_wrapper(): ...";
|
||||||
const ASYNCIO_TASKS_CONTENTS: &str = "class Task: ...";
|
const ASYNCIO_TASKS_CONTENTS: &str = "class Task: ...";
|
||||||
|
|
||||||
static MOCK_ZIP_ARCHIVE: Lazy<Box<[u8]>> = Lazy::new(|| {
|
pub struct VendoredFileSystemBuilder {
|
||||||
let mut typeshed_buffer = Vec::new();
|
writer: ZipWriter<io::Cursor<Vec<u8>>>,
|
||||||
let typeshed = io::Cursor::new(&mut typeshed_buffer);
|
|
||||||
|
|
||||||
let options = FileOptions::default()
|
|
||||||
.compression_method(CompressionMethod::Zstd)
|
|
||||||
.unix_permissions(0o644);
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut archive = ZipWriter::new(typeshed);
|
|
||||||
|
|
||||||
archive.add_directory("stdlib/", options).unwrap();
|
|
||||||
archive.start_file("stdlib/functools.pyi", options).unwrap();
|
|
||||||
archive.write_all(FUNCTOOLS_CONTENTS.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
archive.add_directory("stdlib/asyncio/", options).unwrap();
|
|
||||||
archive
|
|
||||||
.start_file("stdlib/asyncio/tasks.pyi", options)
|
|
||||||
.unwrap();
|
|
||||||
archive
|
|
||||||
.write_all(ASYNCIO_TASKS_CONTENTS.as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
archive.finish().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typeshed_buffer.into_boxed_slice()
|
impl Default for VendoredFileSystemBuilder {
|
||||||
});
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VendoredFileSystemBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let buffer = io::Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
writer: ZipWriter::new(buffer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_file(
|
||||||
|
&mut self,
|
||||||
|
path: impl AsRef<VendoredPath>,
|
||||||
|
content: &str,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
self.writer
|
||||||
|
.start_file(path.as_ref().as_str(), Self::options())?;
|
||||||
|
self.writer.write_all(content.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_directory(&mut self, path: impl AsRef<VendoredPath>) -> ZipResult<()> {
|
||||||
|
self.writer
|
||||||
|
.add_directory(path.as_ref().as_str(), Self::options())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(mut self) -> Result<VendoredFileSystem> {
|
||||||
|
let buffer = self.writer.finish()?;
|
||||||
|
|
||||||
|
VendoredFileSystem::new(buffer.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn options() -> FileOptions {
|
||||||
|
FileOptions::default()
|
||||||
|
.compression_method(CompressionMethod::Zstd)
|
||||||
|
.unix_permissions(0o644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn mock_typeshed() -> VendoredFileSystem {
|
fn mock_typeshed() -> VendoredFileSystem {
|
||||||
VendoredFileSystem::new(&MOCK_ZIP_ARCHIVE).unwrap()
|
let mut builder = VendoredFileSystemBuilder::new();
|
||||||
|
|
||||||
|
builder.add_directory("stdlib/").unwrap();
|
||||||
|
builder
|
||||||
|
.add_file("stdlib/functools.pyi", FUNCTOOLS_CONTENTS)
|
||||||
|
.unwrap();
|
||||||
|
builder.add_directory("stdlib/asyncio/").unwrap();
|
||||||
|
builder
|
||||||
|
.add_file("stdlib/asyncio/tasks.pyi", ASYNCIO_TASKS_CONTENTS)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder.finish().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -395,9 +472,9 @@ mod tests {
|
||||||
let path = VendoredPath::new(dirname);
|
let path = VendoredPath::new(dirname);
|
||||||
|
|
||||||
assert!(mock_typeshed.exists(path));
|
assert!(mock_typeshed.exists(path));
|
||||||
assert!(mock_typeshed.read(path).is_err());
|
assert!(mock_typeshed.read_to_string(path).is_err());
|
||||||
let metadata = mock_typeshed.metadata(path).unwrap();
|
let metadata = mock_typeshed.metadata(path).unwrap();
|
||||||
assert!(metadata.kind.is_directory());
|
assert!(metadata.kind().is_directory());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -434,9 +511,9 @@ mod tests {
|
||||||
let mock_typeshed = mock_typeshed();
|
let mock_typeshed = mock_typeshed();
|
||||||
let path = VendoredPath::new(path);
|
let path = VendoredPath::new(path);
|
||||||
assert!(!mock_typeshed.exists(path));
|
assert!(!mock_typeshed.exists(path));
|
||||||
assert!(mock_typeshed.metadata(path).is_none());
|
assert!(mock_typeshed.metadata(path).is_err());
|
||||||
assert!(mock_typeshed
|
assert!(mock_typeshed
|
||||||
.read(path)
|
.read_to_string(path)
|
||||||
.is_err_and(|err| err.to_string().contains("file not found")));
|
.is_err_and(|err| err.to_string().contains("file not found")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,7 +540,7 @@ mod tests {
|
||||||
fn test_file(mock_typeshed: &VendoredFileSystem, path: &VendoredPath) {
|
fn test_file(mock_typeshed: &VendoredFileSystem, path: &VendoredPath) {
|
||||||
assert!(mock_typeshed.exists(path));
|
assert!(mock_typeshed.exists(path));
|
||||||
let metadata = mock_typeshed.metadata(path).unwrap();
|
let metadata = mock_typeshed.metadata(path).unwrap();
|
||||||
assert!(metadata.kind.is_file());
|
assert!(metadata.kind().is_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -471,11 +548,11 @@ mod tests {
|
||||||
let mock_typeshed = mock_typeshed();
|
let mock_typeshed = mock_typeshed();
|
||||||
let path = VendoredPath::new("stdlib/functools.pyi");
|
let path = VendoredPath::new("stdlib/functools.pyi");
|
||||||
test_file(&mock_typeshed, path);
|
test_file(&mock_typeshed, path);
|
||||||
let functools_stub = mock_typeshed.read(path).unwrap();
|
let functools_stub = mock_typeshed.read_to_string(path).unwrap();
|
||||||
assert_eq!(functools_stub.as_str(), FUNCTOOLS_CONTENTS);
|
assert_eq!(functools_stub.as_str(), FUNCTOOLS_CONTENTS);
|
||||||
// Test that using the RefCell doesn't mutate
|
// Test that using the RefCell doesn't mutate
|
||||||
// the internal state of the underlying zip archive incorrectly:
|
// the internal state of the underlying zip archive incorrectly:
|
||||||
let functools_stub_again = mock_typeshed.read(path).unwrap();
|
let functools_stub_again = mock_typeshed.read_to_string(path).unwrap();
|
||||||
assert_eq!(functools_stub_again.as_str(), FUNCTOOLS_CONTENTS);
|
assert_eq!(functools_stub_again.as_str(), FUNCTOOLS_CONTENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,7 +569,7 @@ mod tests {
|
||||||
let mock_typeshed = mock_typeshed();
|
let mock_typeshed = mock_typeshed();
|
||||||
let path = VendoredPath::new("stdlib/asyncio/tasks.pyi");
|
let path = VendoredPath::new("stdlib/asyncio/tasks.pyi");
|
||||||
test_file(&mock_typeshed, path);
|
test_file(&mock_typeshed, path);
|
||||||
let asyncio_stub = mock_typeshed.read(path).unwrap();
|
let asyncio_stub = mock_typeshed.read_to_string(path).unwrap();
|
||||||
assert_eq!(asyncio_stub.as_str(), ASYNCIO_TASKS_CONTENTS);
|
assert_eq!(asyncio_stub.as_str(), ASYNCIO_TASKS_CONTENTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,161 +0,0 @@
|
||||||
use crate::file_system::{FileSystemPath, FileSystemPathBuf};
|
|
||||||
use crate::vendored::path::{VendoredPath, VendoredPathBuf};
|
|
||||||
|
|
||||||
/// Path to a file.
|
|
||||||
///
|
|
||||||
/// The path abstracts that files in Ruff can come from different sources:
|
|
||||||
///
|
|
||||||
/// * a file stored on disk
|
|
||||||
/// * a vendored file that ships as part of the ruff binary
|
|
||||||
/// * Future: A virtual file that references a slice of another file. For example, the CSS code in a python file.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
||||||
pub enum VfsPath {
|
|
||||||
/// Path that points to a file on disk.
|
|
||||||
FileSystem(FileSystemPathBuf),
|
|
||||||
Vendored(VendoredPathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VfsPath {
|
|
||||||
/// Create a new path to a file on the file system.
|
|
||||||
#[must_use]
|
|
||||||
pub fn file_system(path: impl AsRef<FileSystemPath>) -> Self {
|
|
||||||
VfsPath::FileSystem(path.as_ref().to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `Some` if the path is a file system path that points to a path on disk.
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn into_file_system_path_buf(self) -> Option<FileSystemPathBuf> {
|
|
||||||
match self {
|
|
||||||
VfsPath::FileSystem(path) => Some(path),
|
|
||||||
VfsPath::Vendored(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn as_file_system_path(&self) -> Option<&FileSystemPath> {
|
|
||||||
match self {
|
|
||||||
VfsPath::FileSystem(path) => Some(path.as_path()),
|
|
||||||
VfsPath::Vendored(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the path is a file system path that points to a path on disk.
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub const fn is_file_system_path(&self) -> bool {
|
|
||||||
matches!(self, VfsPath::FileSystem(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the path is a vendored path.
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub const fn is_vendored_path(&self) -> bool {
|
|
||||||
matches!(self, VfsPath::Vendored(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn as_vendored_path(&self) -> Option<&VendoredPath> {
|
|
||||||
match self {
|
|
||||||
VfsPath::Vendored(path) => Some(path.as_path()),
|
|
||||||
VfsPath::FileSystem(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Yields the underlying [`str`] slice.
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
VfsPath::FileSystem(path) => path.as_str(),
|
|
||||||
VfsPath::Vendored(path) => path.as_str(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for VfsPath {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FileSystemPathBuf> for VfsPath {
|
|
||||||
fn from(value: FileSystemPathBuf) -> Self {
|
|
||||||
Self::FileSystem(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&FileSystemPath> for VfsPath {
|
|
||||||
fn from(value: &FileSystemPath) -> Self {
|
|
||||||
VfsPath::FileSystem(value.to_path_buf())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<VendoredPathBuf> for VfsPath {
|
|
||||||
fn from(value: VendoredPathBuf) -> Self {
|
|
||||||
Self::Vendored(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&VendoredPath> for VfsPath {
|
|
||||||
fn from(value: &VendoredPath) -> Self {
|
|
||||||
Self::Vendored(value.to_path_buf())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<FileSystemPath> for VfsPath {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &FileSystemPath) -> bool {
|
|
||||||
self.as_file_system_path()
|
|
||||||
.is_some_and(|self_path| self_path == other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<VfsPath> for FileSystemPath {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &VfsPath) -> bool {
|
|
||||||
other == self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<FileSystemPathBuf> for VfsPath {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &FileSystemPathBuf) -> bool {
|
|
||||||
self == other.as_path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<VfsPath> for FileSystemPathBuf {
|
|
||||||
fn eq(&self, other: &VfsPath) -> bool {
|
|
||||||
other == self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<VendoredPath> for VfsPath {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &VendoredPath) -> bool {
|
|
||||||
self.as_vendored_path()
|
|
||||||
.is_some_and(|self_path| self_path == other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<VfsPath> for VendoredPath {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &VfsPath) -> bool {
|
|
||||||
other == self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<VendoredPathBuf> for VfsPath {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &VendoredPathBuf) -> bool {
|
|
||||||
other.as_path() == self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<VfsPath> for VendoredPathBuf {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &VfsPath) -> bool {
|
|
||||||
other == self
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue