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_index",
"ruff_python_parser",
"ruff_python_resolver",
"ruff_python_semantic",
"ruff_python_stdlib",
"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_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 }

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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,
) {

View File

@ -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>(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::<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);
}
}
}
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,
);

View File

@ -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();

View File

@ -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,

View File

@ -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>,
}

View File

@ -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>,
}

View File

@ -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 }
}
}

View File

@ -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,

View File

@ -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;

View File

@ -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 {

View File

@ -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,

View File

@ -1,6 +1,6 @@
/// Enum to represent a Python version.
#[derive(Debug, Copy, Clone)]
pub(crate) enum PythonVersion {
pub enum PythonVersion {
Py37,
Py38,
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.
/// 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,37 +715,28 @@ 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 {
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()