mirror of https://github.com/astral-sh/ruff
Expose the import resolver to Ruff
This commit is contained in:
parent
2d2673f613
commit
1cb500ffd9
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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<u32>,
|
||||
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>(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::<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
|
||||
}
|
||||
|
||||
#[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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Diagnostic> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>,
|
||||
pub typeshed_path: Option<PathBuf>,
|
||||
|
||||
/// 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
|
||||
/// 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<PathBuf>,
|
||||
pub venv_path: Option<PathBuf>,
|
||||
|
||||
/// Default venv environment.
|
||||
pub(crate) venv: Option<PathBuf>,
|
||||
pub venv: Option<PathBuf>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>,
|
||||
pub extra_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>;
|
||||
|
||||
|
|
@ -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<PathBuf>,
|
||||
}
|
||||
|
||||
impl StaticHost {
|
||||
pub(crate) fn new(search_paths: Vec<PathBuf>) -> Self {
|
||||
pub fn new(search_paths: Vec<PathBuf>) -> Self {
|
||||
Self { search_paths }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ImportModuleDescriptor {
|
||||
pub(crate) leading_dots: usize,
|
||||
pub(crate) name_parts: Vec<String>,
|
||||
pub(crate) imported_symbols: Vec<String>,
|
||||
pub struct ImportModuleDescriptor {
|
||||
pub leading_dots: usize,
|
||||
pub name_parts: Vec<String>,
|
||||
pub imported_symbols: Vec<String>,
|
||||
}
|
||||
|
||||
impl ImportModuleDescriptor {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/// Enum to represent a Python version.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum PythonVersion {
|
||||
pub enum PythonVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
Py39,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
/// 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<Host: host::Host>(
|
||||
pub fn resolve_import<Host: host::Host>(
|
||||
source_file: &Path,
|
||||
execution_environment: &ExecutionEnvironment,
|
||||
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
|
||||
// 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 {
|
||||
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,
|
||||
);
|
||||
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
|
||||
|
|
@ -746,7 +738,6 @@ pub(crate) fn resolve_import<Host: host::Host>(
|
|||
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
|
||||
ImportResult::not_found()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue