diff --git a/Cargo.lock b/Cargo.lock index 08cdd31917..b4b0b84972 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1908,6 +1908,7 @@ dependencies = [ "ruff_python_codegen", "ruff_python_index", "ruff_python_parser", + "ruff_python_resolver", "ruff_python_semantic", "ruff_python_stdlib", "ruff_python_trivia", diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index a9f679f640..fdc3f4f628 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -22,10 +22,11 @@ ruff_macros = { path = "../ruff_macros" } ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] } ruff_python_codegen = { path = "../ruff_python_codegen" } ruff_python_index = { path = "../ruff_python_index" } +ruff_python_parser = { path = "../ruff_python_parser" } +ruff_python_resolver = { path = "../ruff_python_resolver" } ruff_python_semantic = { path = "../ruff_python_semantic" } ruff_python_stdlib = { path = "../ruff_python_stdlib" } ruff_python_trivia = { path = "../ruff_python_trivia" } -ruff_python_parser = { path = "../ruff_python_parser" } ruff_source_file = { path = "../ruff_source_file" } ruff_text_size = { workspace = true } diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 97ff354925..f2ca602bbe 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -70,8 +70,6 @@ mod deferred; pub(crate) struct Checker<'a> { /// The [`Path`] to the file under analysis. path: &'a Path, - /// The [`Path`] to the package containing the current file. - package: Option<&'a Path>, /// The module representation of the current file (e.g., `foo.bar`). module_path: Option<&'a [String]>, /// Whether the current file is a stub (`.pyi`) file. @@ -111,7 +109,6 @@ impl<'a> Checker<'a> { noqa_line_for: &'a NoqaMapping, noqa: flags::Noqa, path: &'a Path, - package: Option<&'a Path>, module: Module<'a>, locator: &'a Locator, stylist: &'a Stylist, @@ -123,7 +120,6 @@ impl<'a> Checker<'a> { noqa_line_for, noqa, path, - package, module_path: module.path(), is_stub: is_python_stub_file(path), locator, @@ -237,11 +233,6 @@ impl<'a> Checker<'a> { self.path } - /// The [`Path`] to the package containing the current file. - pub(crate) const fn package(&self) -> Option<&'a Path> { - self.package - } - /// Returns whether the given rule should be checked. #[inline] pub(crate) const fn enabled(&self, rule: Rule) -> bool { @@ -1854,7 +1845,6 @@ pub(crate) fn check_ast( noqa_line_for, noqa, path, - package, module, locator, stylist, diff --git a/crates/ruff/src/checkers/imports.rs b/crates/ruff/src/checkers/imports.rs index 6d71be0ac8..5dfdba4e5d 100644 --- a/crates/ruff/src/checkers/imports.rs +++ b/crates/ruff/src/checkers/imports.rs @@ -103,9 +103,9 @@ pub(crate) fn check_imports( if settings.rules.enabled(Rule::UnsortedImports) { for block in &blocks { if !block.imports.is_empty() { - if let Some(diagnostic) = isort::rules::organize_imports( - block, locator, stylist, indexer, settings, package, - ) { + if let Some(diagnostic) = + isort::rules::organize_imports(block, locator, stylist, indexer, settings, path) + { diagnostics.push(diagnostic); } } diff --git a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index 4695395b41..d5b650b8d7 100644 --- a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -250,8 +250,8 @@ pub(crate) fn typing_only_runtime_import( let import_type = match categorize( qualified_name, Some(level), + checker.path(), &checker.settings.src, - checker.package(), &checker.settings.isort.known_modules, checker.settings.target_version, ) { diff --git a/crates/ruff/src/rules/isort/categorize.rs b/crates/ruff/src/rules/isort/categorize.rs index 3ed3d8d1a3..daedb41fb6 100644 --- a/crates/ruff/src/rules/isort/categorize.rs +++ b/crates/ruff/src/rules/isort/categorize.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::hash::BuildHasherDefault; +use std::iter; use std::path::{Path, PathBuf}; -use std::{fs, iter}; use log::debug; use rustc_hash::{FxHashMap, FxHashSet}; @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; use ruff_macros::CacheKey; +use ruff_python_resolver; use ruff_python_stdlib::sys::is_known_standard_library; use crate::settings::types::PythonVersion; @@ -57,18 +58,16 @@ enum Reason<'a> { ExtraStandardLibrary, Future, KnownStandardLibrary, - SamePackage, SourceMatch(&'a Path), NoMatch, UserDefinedSection, } -#[allow(clippy::too_many_arguments)] pub(crate) fn categorize<'a>( module_name: &str, level: Option, + path: &Path, src: &[PathBuf], - package: Option<&Path>, known_modules: &'a KnownModules, target_version: PythonVersion, ) -> &'a ImportSection { @@ -88,12 +87,7 @@ pub(crate) fn categorize<'a>( &ImportSection::Known(ImportType::StandardLibrary), Reason::KnownStandardLibrary, ) - } else if same_package(package, module_base) { - ( - &ImportSection::Known(ImportType::FirstParty), - Reason::SamePackage, - ) - } else if let Some(src) = match_sources(src, module_base) { + } else if let Some(src) = match_sources(module_name, path, src) { ( &ImportSection::Known(ImportType::FirstParty), Reason::SourceMatch(src), @@ -112,31 +106,44 @@ pub(crate) fn categorize<'a>( import_type } -fn same_package(package: Option<&Path>, module_base: &str) -> bool { - package.map_or(false, |package| package.ends_with(module_base)) -} - -fn match_sources<'a>(paths: &'a [PathBuf], base: &str) -> Option<&'a Path> { - for path in paths { - if let Ok(metadata) = fs::metadata(path.join(base)) { - if metadata.is_dir() { - return Some(path); - } - } - if let Ok(metadata) = fs::metadata(path.join(format!("{base}.py"))) { - if metadata.is_file() { - return Some(path); +fn match_sources<'a>(module_name: &str, path: &Path, roots: &'a [PathBuf]) -> Option<&'a Path> { + for root in roots { + let import = ruff_python_resolver::resolve_import( + &root, + &ruff_python_resolver::ExecutionEnvironment { + root: root.clone(), + python_version: ruff_python_resolver::PythonVersion::Py37, + python_platform: ruff_python_resolver::PythonPlatform::Darwin, + extra_paths: vec![], + }, + &ruff_python_resolver::ImportModuleDescriptor { + leading_dots: module_name.chars().take_while(|c| *c == '.').count(), + name_parts: module_name + .chars() + .skip_while(|c| *c == '.') + .collect::() + .split('.') + .map(std::string::ToString::to_string) + .collect(), + imported_symbols: Vec::new(), + }, + &ruff_python_resolver::Config::default(), + &ruff_python_resolver::StaticHost::default(), + ); + if import.is_import_found { + if matches!(import.import_type, ruff_python_resolver::ImportType::Local) { + return Some(root); } } } + None } -#[allow(clippy::too_many_arguments)] pub(crate) fn categorize_imports<'a>( block: ImportBlock<'a>, + path: &Path, src: &[PathBuf], - package: Option<&Path>, known_modules: &'a KnownModules, target_version: PythonVersion, ) -> BTreeMap<&'a ImportSection, ImportBlock<'a>> { @@ -146,8 +153,8 @@ pub(crate) fn categorize_imports<'a>( let import_type = categorize( &alias.module_name(), None, + path, src, - package, known_modules, target_version, ); @@ -162,8 +169,8 @@ pub(crate) fn categorize_imports<'a>( let classification = categorize( &import_from.module_name(), import_from.level, + path, src, - package, known_modules, target_version, ); @@ -178,8 +185,8 @@ pub(crate) fn categorize_imports<'a>( let classification = categorize( &import_from.module_name(), import_from.level, + path, src, - package, known_modules, target_version, ); @@ -194,8 +201,8 @@ pub(crate) fn categorize_imports<'a>( let classification = categorize( &import_from.module_name(), import_from.level, + path, src, - package, known_modules, target_version, ); diff --git a/crates/ruff/src/rules/isort/mod.rs b/crates/ruff/src/rules/isort/mod.rs index 55412202d8..64b41bee66 100644 --- a/crates/ruff/src/rules/isort/mod.rs +++ b/crates/ruff/src/rules/isort/mod.rs @@ -70,8 +70,8 @@ pub(crate) fn format_imports( line_length: LineLength, indentation_width: LineWidth, stylist: &Stylist, + path: &Path, src: &[PathBuf], - package: Option<&Path>, combine_as_imports: bool, force_single_line: bool, force_sort_within_sections: bool, @@ -113,8 +113,8 @@ pub(crate) fn format_imports( line_length, indentation_width, stylist, + path, src, - package, force_sort_within_sections, case_sensitive, force_wrap_aliases, @@ -171,8 +171,8 @@ fn format_import_block( line_length: LineLength, indentation_width: LineWidth, stylist: &Stylist, + path: &Path, src: &[PathBuf], - package: Option<&Path>, force_sort_within_sections: bool, case_sensitive: bool, force_wrap_aliases: bool, @@ -190,7 +190,7 @@ fn format_import_block( section_order: &[ImportSection], ) -> String { // Categorize by type (e.g., first-party vs. third-party). - let mut block_by_type = categorize_imports(block, src, package, known_modules, target_version); + let mut block_by_type = categorize_imports(block, path, src, known_modules, target_version); let mut output = String::new(); diff --git a/crates/ruff/src/rules/isort/rules/organize_imports.rs b/crates/ruff/src/rules/isort/rules/organize_imports.rs index fa19caa195..0538c0aa5e 100644 --- a/crates/ruff/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff/src/rules/isort/rules/organize_imports.rs @@ -1,8 +1,7 @@ -use std::path::Path; - use itertools::{EitherOrBoth, Itertools}; use ruff_text_size::TextRange; use rustpython_ast::{Ranged, Stmt}; +use std::path::Path; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; @@ -86,7 +85,7 @@ pub(crate) fn organize_imports( stylist: &Stylist, indexer: &Indexer, settings: &Settings, - package: Option<&Path>, + path: &Path, ) -> Option { let indentation = locator.slice(extract_indentation_range(&block.imports, locator)); let indentation = leading_indentation(indentation); @@ -121,8 +120,8 @@ pub(crate) fn organize_imports( settings.line_length, LineWidth::new(settings.tab_size).add_str(indentation), stylist, + path, &settings.src, - package, settings.isort.combine_as_imports, settings.isort.force_single_line, settings.isort.force_sort_within_sections, diff --git a/crates/ruff_python_resolver/src/config.rs b/crates/ruff_python_resolver/src/config.rs index 072e44a993..28328e43f3 100644 --- a/crates/ruff_python_resolver/src/config.rs +++ b/crates/ruff_python_resolver/src/config.rs @@ -1,18 +1,19 @@ use std::path::PathBuf; -pub(crate) struct Config { +#[derive(Debug, Default)] +pub struct Config { /// Path to use for typeshed definitions. - pub(crate) typeshed_path: Option, + pub typeshed_path: Option, /// Path to custom typings (stub) modules. - pub(crate) stub_path: Option, + pub stub_path: Option, /// Path to a directory containing one or more virtual environment /// directories. This is used in conjunction with the "venv" name in /// the config file to identify the python environment used for resolving /// third-party modules. - pub(crate) venv_path: Option, + pub venv_path: Option, /// Default venv environment. - pub(crate) venv: Option, + pub venv: Option, } diff --git a/crates/ruff_python_resolver/src/execution_environment.rs b/crates/ruff_python_resolver/src/execution_environment.rs index b969ddc42b..5411e127ca 100644 --- a/crates/ruff_python_resolver/src/execution_environment.rs +++ b/crates/ruff_python_resolver/src/execution_environment.rs @@ -4,16 +4,16 @@ use crate::python_platform::PythonPlatform; use crate::python_version::PythonVersion; #[derive(Debug)] -pub(crate) struct ExecutionEnvironment { +pub struct ExecutionEnvironment { /// The root directory of the execution environment. - pub(crate) root: PathBuf, + pub root: PathBuf, /// The Python version of the execution environment. - pub(crate) python_version: PythonVersion, + pub python_version: PythonVersion, /// The Python platform of the execution environment. - pub(crate) python_platform: PythonPlatform, + pub python_platform: PythonPlatform, /// The extra search paths of the execution environment. - pub(crate) extra_paths: Vec, + pub extra_paths: Vec, } diff --git a/crates/ruff_python_resolver/src/host.rs b/crates/ruff_python_resolver/src/host.rs index be9b0a5e60..18dfdc6208 100644 --- a/crates/ruff_python_resolver/src/host.rs +++ b/crates/ruff_python_resolver/src/host.rs @@ -6,7 +6,7 @@ use crate::python_platform::PythonPlatform; use crate::python_version::PythonVersion; /// A trait to expose the host environment to the resolver. -pub(crate) trait Host { +pub trait Host { /// The search paths to use when resolving Python modules. fn python_search_paths(&self) -> Vec; @@ -18,12 +18,13 @@ pub(crate) trait Host { } /// A host that exposes a fixed set of search paths. -pub(crate) struct StaticHost { +#[derive(Debug, Default)] +pub struct StaticHost { search_paths: Vec, } impl StaticHost { - pub(crate) fn new(search_paths: Vec) -> Self { + pub fn new(search_paths: Vec) -> Self { Self { search_paths } } } diff --git a/crates/ruff_python_resolver/src/import_result.rs b/crates/ruff_python_resolver/src/import_result.rs index 704781f420..e3999c2fa7 100644 --- a/crates/ruff_python_resolver/src/import_result.rs +++ b/crates/ruff_python_resolver/src/import_result.rs @@ -7,12 +7,12 @@ use crate::py_typed::PyTypedInfo; #[derive(Debug, Clone, PartialEq, Eq)] #[allow(clippy::struct_excessive_bools)] -pub(crate) struct ImportResult { +pub struct ImportResult { /// Whether the import name was relative (e.g., ".foo"). pub(crate) is_relative: bool, /// Whether the import was resolved to a file or module. - pub(crate) is_import_found: bool, + pub is_import_found: bool, /// The path was partially resolved, but the specific submodule /// defining the import was not found. For example, `foo.bar` was @@ -32,7 +32,7 @@ pub(crate) struct ImportResult { pub(crate) is_stub_package: bool, /// The import resolved to a built-in, local, or third-party module. - pub(crate) import_type: ImportType, + pub import_type: ImportType, /// A vector of resolved absolute paths for each file in the module /// name. Typically includes a sequence of `__init__.py` files, followed @@ -114,7 +114,7 @@ impl ImportResult { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ImportType { +pub enum ImportType { BuiltIn, ThirdParty, Local, diff --git a/crates/ruff_python_resolver/src/lib.rs b/crates/ruff_python_resolver/src/lib.rs index 52be653d6c..a43fd8854f 100644 --- a/crates/ruff_python_resolver/src/lib.rs +++ b/crates/ruff_python_resolver/src/lib.rs @@ -1,5 +1,14 @@ #![allow(dead_code)] +pub use config::*; +pub use execution_environment::*; +pub use host::*; +pub use import_result::*; +pub use module_descriptor::*; +pub use python_platform::*; +pub use python_version::*; +pub use resolver::*; + mod config; mod execution_environment; mod host; diff --git a/crates/ruff_python_resolver/src/module_descriptor.rs b/crates/ruff_python_resolver/src/module_descriptor.rs index 7d71efafbc..b3a93ae04d 100644 --- a/crates/ruff_python_resolver/src/module_descriptor.rs +++ b/crates/ruff_python_resolver/src/module_descriptor.rs @@ -1,8 +1,8 @@ #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct ImportModuleDescriptor { - pub(crate) leading_dots: usize, - pub(crate) name_parts: Vec, - pub(crate) imported_symbols: Vec, +pub struct ImportModuleDescriptor { + pub leading_dots: usize, + pub name_parts: Vec, + pub imported_symbols: Vec, } impl ImportModuleDescriptor { diff --git a/crates/ruff_python_resolver/src/python_platform.rs b/crates/ruff_python_resolver/src/python_platform.rs index b82ebe256c..bf764e79b3 100644 --- a/crates/ruff_python_resolver/src/python_platform.rs +++ b/crates/ruff_python_resolver/src/python_platform.rs @@ -1,6 +1,6 @@ /// Enum to represent a Python platform. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub(crate) enum PythonPlatform { +pub enum PythonPlatform { Darwin, Linux, Windows, diff --git a/crates/ruff_python_resolver/src/python_version.rs b/crates/ruff_python_resolver/src/python_version.rs index aeb2a76b75..025e71ed17 100644 --- a/crates/ruff_python_resolver/src/python_version.rs +++ b/crates/ruff_python_resolver/src/python_version.rs @@ -1,6 +1,6 @@ /// Enum to represent a Python version. #[derive(Debug, Copy, Clone)] -pub(crate) enum PythonVersion { +pub enum PythonVersion { Py37, Py38, Py39, diff --git a/crates/ruff_python_resolver/src/resolver.rs b/crates/ruff_python_resolver/src/resolver.rs index 86b2d5e5b8..175ec7ffa9 100644 --- a/crates/ruff_python_resolver/src/resolver.rs +++ b/crates/ruff_python_resolver/src/resolver.rs @@ -693,7 +693,7 @@ fn resolve_import_strict( /// 3. If a stub file was found, find the "best" match for the import, disallowing stub files. /// 4. If the import wasn't resolved, try to resolve it in the parent directory, then the parent's /// parent, and so on, until the import root is reached. -pub(crate) fn resolve_import( +pub fn resolve_import( source_file: &Path, execution_environment: &ExecutionEnvironment, module_descriptor: &ImportModuleDescriptor, @@ -715,37 +715,28 @@ pub(crate) fn resolve_import( // importing file's directory, then the parent directory, and so on, until the // import root is reached. let root = execution_environment.root.as_path(); - if source_file.starts_with(root) { - let mut current = source_file; - while let Some(parent) = current.parent() { - if parent == root { - break; - } - - debug!("Resolving absolute import in parent: {}", parent.display()); - - let mut result = resolve_absolute_import( - parent, - module_descriptor, - false, - false, - false, - true, - false, - ); - - if result.is_import_found { - if let Some(implicit_imports) = result - .implicit_imports - .filter(&module_descriptor.imported_symbols) - { - result.implicit_imports = implicit_imports; - } - return result; - } - - current = parent; + let mut current = source_file; + while let Some(parent) = current.parent() { + if !parent.starts_with(root) { + break; } + + debug!("Resolving absolute import in parent: {}", parent.display()); + + let mut result = + resolve_absolute_import(parent, module_descriptor, false, false, false, true, false); + + if result.is_import_found { + if let Some(implicit_imports) = result + .implicit_imports + .filter(&module_descriptor.imported_symbols) + { + result.implicit_imports = implicit_imports; + } + return result; + } + + current = parent; } ImportResult::not_found()