Expose the import resolver to Ruff

This commit is contained in:
Charlie Marsh 2023-06-27 16:29:46 -04:00
parent 2d2673f613
commit 1cb500ffd9
17 changed files with 107 additions and 107 deletions

1
Cargo.lock generated
View File

@ -1908,6 +1908,7 @@ dependencies = [
"ruff_python_codegen", "ruff_python_codegen",
"ruff_python_index", "ruff_python_index",
"ruff_python_parser", "ruff_python_parser",
"ruff_python_resolver",
"ruff_python_semantic", "ruff_python_semantic",
"ruff_python_stdlib", "ruff_python_stdlib",
"ruff_python_trivia", "ruff_python_trivia",

View File

@ -22,10 +22,11 @@ ruff_macros = { path = "../ruff_macros" }
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] } ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
ruff_python_codegen = { path = "../ruff_python_codegen" } ruff_python_codegen = { path = "../ruff_python_codegen" }
ruff_python_index = { path = "../ruff_python_index" } 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_semantic = { path = "../ruff_python_semantic" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" } ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_python_trivia = { path = "../ruff_python_trivia" } ruff_python_trivia = { path = "../ruff_python_trivia" }
ruff_python_parser = { path = "../ruff_python_parser" }
ruff_source_file = { path = "../ruff_source_file" } ruff_source_file = { path = "../ruff_source_file" }
ruff_text_size = { workspace = true } ruff_text_size = { workspace = true }

View File

@ -70,8 +70,6 @@ mod deferred;
pub(crate) struct Checker<'a> { pub(crate) struct Checker<'a> {
/// The [`Path`] to the file under analysis. /// The [`Path`] to the file under analysis.
path: &'a Path, 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`). /// The module representation of the current file (e.g., `foo.bar`).
module_path: Option<&'a [String]>, module_path: Option<&'a [String]>,
/// Whether the current file is a stub (`.pyi`) file. /// Whether the current file is a stub (`.pyi`) file.
@ -111,7 +109,6 @@ impl<'a> Checker<'a> {
noqa_line_for: &'a NoqaMapping, noqa_line_for: &'a NoqaMapping,
noqa: flags::Noqa, noqa: flags::Noqa,
path: &'a Path, path: &'a Path,
package: Option<&'a Path>,
module: Module<'a>, module: Module<'a>,
locator: &'a Locator, locator: &'a Locator,
stylist: &'a Stylist, stylist: &'a Stylist,
@ -123,7 +120,6 @@ impl<'a> Checker<'a> {
noqa_line_for, noqa_line_for,
noqa, noqa,
path, path,
package,
module_path: module.path(), module_path: module.path(),
is_stub: is_python_stub_file(path), is_stub: is_python_stub_file(path),
locator, locator,
@ -237,11 +233,6 @@ impl<'a> Checker<'a> {
self.path 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. /// Returns whether the given rule should be checked.
#[inline] #[inline]
pub(crate) const fn enabled(&self, rule: Rule) -> bool { pub(crate) const fn enabled(&self, rule: Rule) -> bool {
@ -1854,7 +1845,6 @@ pub(crate) fn check_ast(
noqa_line_for, noqa_line_for,
noqa, noqa,
path, path,
package,
module, module,
locator, locator,
stylist, stylist,

View File

@ -103,9 +103,9 @@ pub(crate) fn check_imports(
if settings.rules.enabled(Rule::UnsortedImports) { if settings.rules.enabled(Rule::UnsortedImports) {
for block in &blocks { for block in &blocks {
if !block.imports.is_empty() { if !block.imports.is_empty() {
if let Some(diagnostic) = isort::rules::organize_imports( if let Some(diagnostic) =
block, locator, stylist, indexer, settings, package, isort::rules::organize_imports(block, locator, stylist, indexer, settings, path)
) { {
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
} }

View File

@ -250,8 +250,8 @@ pub(crate) fn typing_only_runtime_import(
let import_type = match categorize( let import_type = match categorize(
qualified_name, qualified_name,
Some(level), Some(level),
checker.path(),
&checker.settings.src, &checker.settings.src,
checker.package(),
&checker.settings.isort.known_modules, &checker.settings.isort.known_modules,
checker.settings.target_version, checker.settings.target_version,
) { ) {

View File

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use std::iter;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, iter};
use log::debug; use log::debug;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use strum_macros::EnumIter; use strum_macros::EnumIter;
use ruff_macros::CacheKey; use ruff_macros::CacheKey;
use ruff_python_resolver;
use ruff_python_stdlib::sys::is_known_standard_library; use ruff_python_stdlib::sys::is_known_standard_library;
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
@ -57,18 +58,16 @@ enum Reason<'a> {
ExtraStandardLibrary, ExtraStandardLibrary,
Future, Future,
KnownStandardLibrary, KnownStandardLibrary,
SamePackage,
SourceMatch(&'a Path), SourceMatch(&'a Path),
NoMatch, NoMatch,
UserDefinedSection, UserDefinedSection,
} }
#[allow(clippy::too_many_arguments)]
pub(crate) fn categorize<'a>( pub(crate) fn categorize<'a>(
module_name: &str, module_name: &str,
level: Option<u32>, level: Option<u32>,
path: &Path,
src: &[PathBuf], src: &[PathBuf],
package: Option<&Path>,
known_modules: &'a KnownModules, known_modules: &'a KnownModules,
target_version: PythonVersion, target_version: PythonVersion,
) -> &'a ImportSection { ) -> &'a ImportSection {
@ -88,12 +87,7 @@ pub(crate) fn categorize<'a>(
&ImportSection::Known(ImportType::StandardLibrary), &ImportSection::Known(ImportType::StandardLibrary),
Reason::KnownStandardLibrary, Reason::KnownStandardLibrary,
) )
} else if same_package(package, module_base) { } else if let Some(src) = match_sources(module_name, path, src) {
(
&ImportSection::Known(ImportType::FirstParty),
Reason::SamePackage,
)
} else if let Some(src) = match_sources(src, module_base) {
( (
&ImportSection::Known(ImportType::FirstParty), &ImportSection::Known(ImportType::FirstParty),
Reason::SourceMatch(src), Reason::SourceMatch(src),
@ -112,31 +106,44 @@ pub(crate) fn categorize<'a>(
import_type import_type
} }
fn same_package(package: Option<&Path>, module_base: &str) -> bool { fn match_sources<'a>(module_name: &str, path: &Path, roots: &'a [PathBuf]) -> Option<&'a Path> {
package.map_or(false, |package| package.ends_with(module_base)) 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::<String>()
.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);
}
}
} }
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);
}
}
}
None None
} }
#[allow(clippy::too_many_arguments)]
pub(crate) fn categorize_imports<'a>( pub(crate) fn categorize_imports<'a>(
block: ImportBlock<'a>, block: ImportBlock<'a>,
path: &Path,
src: &[PathBuf], src: &[PathBuf],
package: Option<&Path>,
known_modules: &'a KnownModules, known_modules: &'a KnownModules,
target_version: PythonVersion, target_version: PythonVersion,
) -> BTreeMap<&'a ImportSection, ImportBlock<'a>> { ) -> BTreeMap<&'a ImportSection, ImportBlock<'a>> {
@ -146,8 +153,8 @@ pub(crate) fn categorize_imports<'a>(
let import_type = categorize( let import_type = categorize(
&alias.module_name(), &alias.module_name(),
None, None,
path,
src, src,
package,
known_modules, known_modules,
target_version, target_version,
); );
@ -162,8 +169,8 @@ pub(crate) fn categorize_imports<'a>(
let classification = categorize( let classification = categorize(
&import_from.module_name(), &import_from.module_name(),
import_from.level, import_from.level,
path,
src, src,
package,
known_modules, known_modules,
target_version, target_version,
); );
@ -178,8 +185,8 @@ pub(crate) fn categorize_imports<'a>(
let classification = categorize( let classification = categorize(
&import_from.module_name(), &import_from.module_name(),
import_from.level, import_from.level,
path,
src, src,
package,
known_modules, known_modules,
target_version, target_version,
); );
@ -194,8 +201,8 @@ pub(crate) fn categorize_imports<'a>(
let classification = categorize( let classification = categorize(
&import_from.module_name(), &import_from.module_name(),
import_from.level, import_from.level,
path,
src, src,
package,
known_modules, known_modules,
target_version, target_version,
); );

View File

@ -70,8 +70,8 @@ pub(crate) fn format_imports(
line_length: LineLength, line_length: LineLength,
indentation_width: LineWidth, indentation_width: LineWidth,
stylist: &Stylist, stylist: &Stylist,
path: &Path,
src: &[PathBuf], src: &[PathBuf],
package: Option<&Path>,
combine_as_imports: bool, combine_as_imports: bool,
force_single_line: bool, force_single_line: bool,
force_sort_within_sections: bool, force_sort_within_sections: bool,
@ -113,8 +113,8 @@ pub(crate) fn format_imports(
line_length, line_length,
indentation_width, indentation_width,
stylist, stylist,
path,
src, src,
package,
force_sort_within_sections, force_sort_within_sections,
case_sensitive, case_sensitive,
force_wrap_aliases, force_wrap_aliases,
@ -171,8 +171,8 @@ fn format_import_block(
line_length: LineLength, line_length: LineLength,
indentation_width: LineWidth, indentation_width: LineWidth,
stylist: &Stylist, stylist: &Stylist,
path: &Path,
src: &[PathBuf], src: &[PathBuf],
package: Option<&Path>,
force_sort_within_sections: bool, force_sort_within_sections: bool,
case_sensitive: bool, case_sensitive: bool,
force_wrap_aliases: bool, force_wrap_aliases: bool,
@ -190,7 +190,7 @@ fn format_import_block(
section_order: &[ImportSection], section_order: &[ImportSection],
) -> String { ) -> String {
// Categorize by type (e.g., first-party vs. third-party). // 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(); let mut output = String::new();

View File

@ -1,8 +1,7 @@
use std::path::Path;
use itertools::{EitherOrBoth, Itertools}; use itertools::{EitherOrBoth, Itertools};
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_ast::{Ranged, Stmt}; use rustpython_ast::{Ranged, Stmt};
use std::path::Path;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -86,7 +85,7 @@ pub(crate) fn organize_imports(
stylist: &Stylist, stylist: &Stylist,
indexer: &Indexer, indexer: &Indexer,
settings: &Settings, settings: &Settings,
package: Option<&Path>, path: &Path,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
let indentation = locator.slice(extract_indentation_range(&block.imports, locator)); let indentation = locator.slice(extract_indentation_range(&block.imports, locator));
let indentation = leading_indentation(indentation); let indentation = leading_indentation(indentation);
@ -121,8 +120,8 @@ pub(crate) fn organize_imports(
settings.line_length, settings.line_length,
LineWidth::new(settings.tab_size).add_str(indentation), LineWidth::new(settings.tab_size).add_str(indentation),
stylist, stylist,
path,
&settings.src, &settings.src,
package,
settings.isort.combine_as_imports, settings.isort.combine_as_imports,
settings.isort.force_single_line, settings.isort.force_single_line,
settings.isort.force_sort_within_sections, settings.isort.force_sort_within_sections,

View File

@ -1,18 +1,19 @@
use std::path::PathBuf; use std::path::PathBuf;
pub(crate) struct Config { #[derive(Debug, Default)]
pub struct Config {
/// Path to use for typeshed definitions. /// Path to use for typeshed definitions.
pub(crate) typeshed_path: Option<PathBuf>, pub typeshed_path: Option<PathBuf>,
/// Path to custom typings (stub) modules. /// Path to custom typings (stub) modules.
pub(crate) stub_path: Option<PathBuf>, pub stub_path: Option<PathBuf>,
/// Path to a directory containing one or more virtual environment /// Path to a directory containing one or more virtual environment
/// directories. This is used in conjunction with the "venv" name in /// directories. This is used in conjunction with the "venv" name in
/// the config file to identify the python environment used for resolving /// the config file to identify the python environment used for resolving
/// third-party modules. /// third-party modules.
pub(crate) venv_path: Option<PathBuf>, pub venv_path: Option<PathBuf>,
/// Default venv environment. /// Default venv environment.
pub(crate) venv: Option<PathBuf>, pub venv: Option<PathBuf>,
} }

View File

@ -4,16 +4,16 @@ use crate::python_platform::PythonPlatform;
use crate::python_version::PythonVersion; use crate::python_version::PythonVersion;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ExecutionEnvironment { pub struct ExecutionEnvironment {
/// The root directory of the execution environment. /// The root directory of the execution environment.
pub(crate) root: PathBuf, pub root: PathBuf,
/// The Python version of the execution environment. /// The Python version of the execution environment.
pub(crate) python_version: PythonVersion, pub python_version: PythonVersion,
/// The Python platform of the execution environment. /// The Python platform of the execution environment.
pub(crate) python_platform: PythonPlatform, pub python_platform: PythonPlatform,
/// The extra search paths of the execution environment. /// The extra search paths of the execution environment.
pub(crate) extra_paths: Vec<PathBuf>, pub extra_paths: Vec<PathBuf>,
} }

View File

@ -6,7 +6,7 @@ use crate::python_platform::PythonPlatform;
use crate::python_version::PythonVersion; use crate::python_version::PythonVersion;
/// A trait to expose the host environment to the resolver. /// 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. /// The search paths to use when resolving Python modules.
fn python_search_paths(&self) -> Vec<PathBuf>; fn python_search_paths(&self) -> Vec<PathBuf>;
@ -18,12 +18,13 @@ pub(crate) trait Host {
} }
/// A host that exposes a fixed set of search paths. /// A host that exposes a fixed set of search paths.
pub(crate) struct StaticHost { #[derive(Debug, Default)]
pub struct StaticHost {
search_paths: Vec<PathBuf>, search_paths: Vec<PathBuf>,
} }
impl StaticHost { impl StaticHost {
pub(crate) fn new(search_paths: Vec<PathBuf>) -> Self { pub fn new(search_paths: Vec<PathBuf>) -> Self {
Self { search_paths } Self { search_paths }
} }
} }

View File

@ -7,12 +7,12 @@ use crate::py_typed::PyTypedInfo;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub(crate) struct ImportResult { pub struct ImportResult {
/// Whether the import name was relative (e.g., ".foo"). /// Whether the import name was relative (e.g., ".foo").
pub(crate) is_relative: bool, pub(crate) is_relative: bool,
/// Whether the import was resolved to a file or module. /// 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 /// The path was partially resolved, but the specific submodule
/// defining the import was not found. For example, `foo.bar` was /// 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, pub(crate) is_stub_package: bool,
/// The import resolved to a built-in, local, or third-party module. /// 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 /// A vector of resolved absolute paths for each file in the module
/// name. Typically includes a sequence of `__init__.py` files, followed /// name. Typically includes a sequence of `__init__.py` files, followed
@ -114,7 +114,7 @@ impl ImportResult {
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ImportType { pub enum ImportType {
BuiltIn, BuiltIn,
ThirdParty, ThirdParty,
Local, Local,

View File

@ -1,5 +1,14 @@
#![allow(dead_code)] #![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 config;
mod execution_environment; mod execution_environment;
mod host; mod host;

View File

@ -1,8 +1,8 @@
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ImportModuleDescriptor { pub struct ImportModuleDescriptor {
pub(crate) leading_dots: usize, pub leading_dots: usize,
pub(crate) name_parts: Vec<String>, pub name_parts: Vec<String>,
pub(crate) imported_symbols: Vec<String>, pub imported_symbols: Vec<String>,
} }
impl ImportModuleDescriptor { impl ImportModuleDescriptor {

View File

@ -1,6 +1,6 @@
/// Enum to represent a Python platform. /// Enum to represent a Python platform.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum PythonPlatform { pub enum PythonPlatform {
Darwin, Darwin,
Linux, Linux,
Windows, Windows,

View File

@ -1,6 +1,6 @@
/// Enum to represent a Python version. /// Enum to represent a Python version.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(crate) enum PythonVersion { pub enum PythonVersion {
Py37, Py37,
Py38, Py38,
Py39, Py39,

View File

@ -693,7 +693,7 @@ fn resolve_import_strict<Host: host::Host>(
/// 3. If a stub file was found, find the "best" match for the import, disallowing stub files. /// 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 /// 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. /// parent, and so on, until the import root is reached.
pub(crate) fn resolve_import<Host: host::Host>( pub fn resolve_import<Host: host::Host>(
source_file: &Path, source_file: &Path,
execution_environment: &ExecutionEnvironment, execution_environment: &ExecutionEnvironment,
module_descriptor: &ImportModuleDescriptor, module_descriptor: &ImportModuleDescriptor,
@ -715,24 +715,16 @@ pub(crate) fn resolve_import<Host: host::Host>(
// importing file's directory, then the parent directory, and so on, until the // importing file's directory, then the parent directory, and so on, until the
// import root is reached. // import root is reached.
let root = execution_environment.root.as_path(); let root = execution_environment.root.as_path();
if source_file.starts_with(root) {
let mut current = source_file; let mut current = source_file;
while let Some(parent) = current.parent() { while let Some(parent) = current.parent() {
if parent == root { if !parent.starts_with(root) {
break; break;
} }
debug!("Resolving absolute import in parent: {}", parent.display()); debug!("Resolving absolute import in parent: {}", parent.display());
let mut result = resolve_absolute_import( let mut result =
parent, resolve_absolute_import(parent, module_descriptor, false, false, false, true, false);
module_descriptor,
false,
false,
false,
true,
false,
);
if result.is_import_found { if result.is_import_found {
if let Some(implicit_imports) = result if let Some(implicit_imports) = result
@ -746,7 +738,6 @@ pub(crate) fn resolve_import<Host: host::Host>(
current = parent; current = parent;
} }
}
ImportResult::not_found() ImportResult::not_found()
} }