From b264067f310e4f9c84e641680b02c5d8fbb91941 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 30 Apr 2023 22:11:23 +0530 Subject: [PATCH 1/2] Show settings path in `--show-settings` output --- crates/ruff/src/resolver.rs | 6 +++++- crates/ruff/src/settings/mod.rs | 13 ++++++++++++- crates/ruff_cli/src/commands/run.rs | 6 +++++- crates/ruff_cli/src/commands/show_settings.rs | 3 +++ crates/ruff_cli/src/resolve.rs | 4 ++-- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/ruff/src/resolver.rs b/crates/ruff/src/resolver.rs index 41ac96c7d142c..1c1855c46ae8c 100644 --- a/crates/ruff/src/resolver.rs +++ b/crates/ruff/src/resolver.rs @@ -166,7 +166,11 @@ pub fn resolve_scoped_settings( ) -> Result<(PathBuf, AllSettings)> { let configuration = resolve_configuration(pyproject, relativity, processor)?; let project_root = relativity.resolve(pyproject); - let settings = AllSettings::from_configuration(configuration, &project_root)?; + let settings = AllSettings::from_configuration( + configuration, + &project_root, + Some(pyproject.to_path_buf()), + )?; Ok((project_root, settings)) } diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index 4f83bb43491d3..e4110aa979ec4 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -13,6 +13,7 @@ use strum::IntoEnumIterator; use ruff_cache::cache_dir; use ruff_macros::CacheKey; +use crate::fs; use crate::registry::{Rule, RuleNamespace, RuleSet, INCOMPATIBLE_CODES}; use crate::rule_selector::{RuleSelector, Specificity}; use crate::rules::{ @@ -43,10 +44,15 @@ const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); pub struct AllSettings { pub cli: CliSettings, pub lib: Settings, + settings_path: Option, } impl AllSettings { - pub fn from_configuration(config: Configuration, project_root: &Path) -> Result { + pub fn from_configuration( + config: Configuration, + project_root: &Path, + settings_path: Option, + ) -> Result { Ok(Self { cli: CliSettings { cache_dir: config @@ -60,8 +66,13 @@ impl AllSettings { update_check: config.update_check.unwrap_or_default(), }, lib: Settings::from_configuration(config, project_root)?, + settings_path: settings_path.map(fs::normalize_path), }) } + + pub fn settings_path(&self) -> Option<&Path> { + self.settings_path.as_deref() + } } #[derive(Debug, Default, Clone)] diff --git a/crates/ruff_cli/src/commands/run.rs b/crates/ruff_cli/src/commands/run.rs index 64da002afc939..320e8523e1bc9 100644 --- a/crates/ruff_cli/src/commands/run.rs +++ b/crates/ruff_cli/src/commands/run.rs @@ -238,7 +238,11 @@ mod test { let diagnostics = run( &[root_path.join("valid.ipynb")], - &PyprojectDiscovery::Fixed(AllSettings::from_configuration(configuration, &root_path)?), + &PyprojectDiscovery::Fixed(AllSettings::from_configuration( + configuration, + &root_path, + None, + )?), &overrides, Cache::Disabled, Noqa::Enabled, diff --git a/crates/ruff_cli/src/commands/show_settings.rs b/crates/ruff_cli/src/commands/show_settings.rs index 6d6cf34c46732..be18d0921b684 100644 --- a/crates/ruff_cli/src/commands/show_settings.rs +++ b/crates/ruff_cli/src/commands/show_settings.rs @@ -30,6 +30,9 @@ pub fn show_settings( let mut stdout = BufWriter::new(io::stdout().lock()); writeln!(stdout, "Resolved settings for: {path:?}")?; + if let Some(settings_path) = pyproject_strategy.top_level_settings().settings_path() { + writeln!(stdout, "Settings path: {settings_path:?}")?; + } writeln!(stdout, "{settings:#?}")?; Ok(()) diff --git a/crates/ruff_cli/src/resolve.rs b/crates/ruff_cli/src/resolve.rs index 40d3d809ca2ad..4a175db15e816 100644 --- a/crates/ruff_cli/src/resolve.rs +++ b/crates/ruff_cli/src/resolve.rs @@ -23,7 +23,7 @@ pub fn resolve( if isolated { let mut config = Configuration::default(); overrides.process_config(&mut config); - let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?; + let settings = AllSettings::from_configuration(config, &path_dedot::CWD, None)?; return Ok(PyprojectDiscovery::Fixed(settings)); } @@ -68,6 +68,6 @@ pub fn resolve( // as the "default" settings.) let mut config = Configuration::default(); overrides.process_config(&mut config); - let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?; + let settings = AllSettings::from_configuration(config, &path_dedot::CWD, None)?; Ok(PyprojectDiscovery::Hierarchical(settings)) } From 14e880b26ada425bf4550fe6738ef798f3c0c51a Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 3 May 2023 22:09:24 +0530 Subject: [PATCH 2/2] Use `PyprojectConfig` to store strategy, settings, path --- crates/ruff/src/packaging.rs | 8 +- crates/ruff/src/resolver.rs | 116 ++++++++++-------- crates/ruff/src/settings/mod.rs | 13 +- crates/ruff_cli/src/commands/add_noqa.rs | 10 +- crates/ruff_cli/src/commands/run.rs | 32 ++--- crates/ruff_cli/src/commands/run_stdin.rs | 22 ++-- crates/ruff_cli/src/commands/show_files.rs | 6 +- crates/ruff_cli/src/commands/show_settings.rs | 10 +- crates/ruff_cli/src/lib.rs | 22 ++-- crates/ruff_cli/src/resolve.rs | 39 ++++-- 10 files changed, 153 insertions(+), 125 deletions(-) diff --git a/crates/ruff/src/packaging.rs b/crates/ruff/src/packaging.rs index 59f0400b0a59e..c80eedecd0e2e 100644 --- a/crates/ruff/src/packaging.rs +++ b/crates/ruff/src/packaging.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use rustc_hash::FxHashMap; -use crate::resolver::{PyprojectDiscovery, Resolver}; +use crate::resolver::{PyprojectConfig, Resolver}; // If we have a Python package layout like: // - root/ @@ -82,7 +82,7 @@ fn detect_package_root_with_cache<'a>( pub fn detect_package_roots<'a>( files: &[&'a Path], resolver: &'a Resolver, - pyproject_strategy: &'a PyprojectDiscovery, + pyproject_config: &'a PyprojectConfig, ) -> FxHashMap<&'a Path, Option<&'a Path>> { // Pre-populate the module cache, since the list of files could (but isn't // required to) contain some `__init__.py` files. @@ -98,9 +98,7 @@ pub fn detect_package_roots<'a>( // Search for the package root for each file. let mut package_roots: FxHashMap<&Path, Option<&Path>> = FxHashMap::default(); for file in files { - let namespace_packages = &resolver - .resolve(file, pyproject_strategy) - .namespace_packages; + let namespace_packages = &resolver.resolve(file, pyproject_config).namespace_packages; if let Some(package) = file.parent() { if package_roots.contains_key(package) { continue; diff --git a/crates/ruff/src/resolver.rs b/crates/ruff/src/resolver.rs index 1c1855c46ae8c..0ad0fa244ed86 100644 --- a/crates/ruff/src/resolver.rs +++ b/crates/ruff/src/resolver.rs @@ -17,25 +17,42 @@ use crate::settings::configuration::Configuration; use crate::settings::pyproject::settings_toml; use crate::settings::{pyproject, AllSettings, Settings}; +/// The configuration information from a `pyproject.toml` file. +pub struct PyprojectConfig { + /// The strategy used to discover the relevant `pyproject.toml` file for + /// each Python file. + pub strategy: PyprojectDiscoveryStrategy, + /// All settings from the `pyproject.toml` file. + pub settings: AllSettings, + /// Absolute path to the `pyproject.toml` file. This would be `None` when + /// either using the default settings or the `--isolated` flag is set. + pub path: Option, +} + +impl PyprojectConfig { + pub fn new( + strategy: PyprojectDiscoveryStrategy, + settings: AllSettings, + path: Option, + ) -> Self { + Self { + strategy, + settings, + path: path.map(fs::normalize_path), + } + } +} + /// The strategy used to discover the relevant `pyproject.toml` file for each /// Python file. #[derive(Debug, is_macro::Is)] -pub enum PyprojectDiscovery { +pub enum PyprojectDiscoveryStrategy { /// Use a fixed `pyproject.toml` file for all Python files (i.e., one /// provided on the command-line). - Fixed(AllSettings), + Fixed, /// Use the closest `pyproject.toml` file in the filesystem hierarchy, or /// the default settings. - Hierarchical(AllSettings), -} - -impl PyprojectDiscovery { - pub fn top_level_settings(&self) -> &AllSettings { - match self { - PyprojectDiscovery::Fixed(settings) => settings, - PyprojectDiscovery::Hierarchical(settings) => settings, - } - } + Hierarchical, } /// The strategy for resolving file paths in a `pyproject.toml`. @@ -75,21 +92,25 @@ impl Resolver { pub fn resolve_all<'a>( &'a self, path: &Path, - strategy: &'a PyprojectDiscovery, + pyproject_config: &'a PyprojectConfig, ) -> &'a AllSettings { - match strategy { - PyprojectDiscovery::Fixed(settings) => settings, - PyprojectDiscovery::Hierarchical(default) => self + match pyproject_config.strategy { + PyprojectDiscoveryStrategy::Fixed => &pyproject_config.settings, + PyprojectDiscoveryStrategy::Hierarchical => self .settings .iter() .rev() .find_map(|(root, settings)| path.starts_with(root).then_some(settings)) - .unwrap_or(default), + .unwrap_or(&pyproject_config.settings), } } - pub fn resolve<'a>(&'a self, path: &Path, strategy: &'a PyprojectDiscovery) -> &'a Settings { - &self.resolve_all(path, strategy).lib + pub fn resolve<'a>( + &'a self, + path: &Path, + pyproject_config: &'a PyprojectConfig, + ) -> &'a Settings { + &self.resolve_all(path, pyproject_config).lib } /// Return an iterator over the resolved [`Settings`] in this [`Resolver`]. @@ -166,11 +187,7 @@ pub fn resolve_scoped_settings( ) -> Result<(PathBuf, AllSettings)> { let configuration = resolve_configuration(pyproject, relativity, processor)?; let project_root = relativity.resolve(pyproject); - let settings = AllSettings::from_configuration( - configuration, - &project_root, - Some(pyproject.to_path_buf()), - )?; + let settings = AllSettings::from_configuration(configuration, &project_root)?; Ok((project_root, settings)) } @@ -204,7 +221,7 @@ fn match_exclusion, R: AsRef>( /// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths. pub fn python_files_in_path( paths: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, processor: impl ConfigProcessor, ) -> Result<(Vec>, Resolver)> { // Normalize every path (e.g., convert from relative to absolute). @@ -213,7 +230,7 @@ pub fn python_files_in_path( // Search for `pyproject.toml` files in all parent directories. let mut resolver = Resolver::default(); let mut seen = FxHashSet::default(); - if pyproject_strategy.is_hierarchical() { + if pyproject_config.strategy.is_hierarchical() { for path in &paths { for ancestor in path.ancestors() { if seen.insert(ancestor) { @@ -228,8 +245,8 @@ pub fn python_files_in_path( } // Check if the paths themselves are excluded. - if pyproject_strategy.top_level_settings().lib.force_exclude { - paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy)); + if pyproject_config.settings.lib.force_exclude { + paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_config)); if paths.is_empty() { return Ok((vec![], resolver)); } @@ -244,12 +261,7 @@ pub fn python_files_in_path( for path in &paths[1..] { builder.add(path); } - builder.standard_filters( - pyproject_strategy - .top_level_settings() - .lib - .respect_gitignore, - ); + builder.standard_filters(pyproject_config.settings.lib.respect_gitignore); builder.hidden(false); let walker = builder.build_parallel(); @@ -265,7 +277,7 @@ pub fn python_files_in_path( if entry.depth() > 0 { let path = entry.path(); let resolver = resolver.read().unwrap(); - let settings = resolver.resolve(path, pyproject_strategy); + let settings = resolver.resolve(path, pyproject_config); if let Some(file_name) = path.file_name() { if !settings.exclude.is_empty() && match_exclusion(path, file_name, &settings.exclude) @@ -287,7 +299,7 @@ pub fn python_files_in_path( // Search for the `pyproject.toml` file in this directory, before we visit any // of its contents. - if pyproject_strategy.is_hierarchical() { + if pyproject_config.strategy.is_hierarchical() { if let Ok(entry) = &result { if entry .file_type() @@ -325,7 +337,7 @@ pub fn python_files_in_path( // Otherwise, check if the file is included. let path = entry.path(); let resolver = resolver.read().unwrap(); - let settings = resolver.resolve(path, pyproject_strategy); + let settings = resolver.resolve(path, pyproject_config); if settings.include.is_match(path) { debug!("Included path via `include`: {:?}", path); true @@ -352,10 +364,10 @@ pub fn python_files_in_path( /// Return `true` if the Python file at [`Path`] is _not_ excluded. pub fn python_file_at_path( path: &Path, - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, processor: impl ConfigProcessor, ) -> Result { - if !pyproject_strategy.top_level_settings().lib.force_exclude { + if !pyproject_config.settings.lib.force_exclude { return Ok(true); } @@ -364,7 +376,7 @@ pub fn python_file_at_path( // Search for `pyproject.toml` files in all parent directories. let mut resolver = Resolver::default(); - if pyproject_strategy.is_hierarchical() { + if pyproject_config.strategy.is_hierarchical() { for ancestor in path.ancestors() { if let Some(pyproject) = settings_toml(ancestor)? { let (root, settings) = @@ -375,14 +387,14 @@ pub fn python_file_at_path( } // Check exclusions. - Ok(!is_file_excluded(&path, &resolver, pyproject_strategy)) + Ok(!is_file_excluded(&path, &resolver, pyproject_config)) } /// Return `true` if the given top-level [`Path`] should be excluded. fn is_file_excluded( path: &Path, resolver: &Resolver, - pyproject_strategy: &PyprojectDiscovery, + pyproject_strategy: &PyprojectConfig, ) -> bool { // TODO(charlie): Respect gitignore. for path in path.ancestors() { @@ -423,7 +435,7 @@ mod tests { use crate::resolver::{ is_file_excluded, match_exclusion, resolve_settings_with_processor, NoOpProcessor, - PyprojectDiscovery, Relativity, Resolver, + PyprojectConfig, PyprojectDiscoveryStrategy, Relativity, Resolver, }; use crate::settings::pyproject::find_settings_toml; use crate::settings::types::FilePattern; @@ -564,25 +576,29 @@ mod tests { fn rooted_exclusion() -> Result<()> { let package_root = test_resource_path("package"); let resolver = Resolver::default(); - let ppd = PyprojectDiscovery::Hierarchical(resolve_settings_with_processor( - &find_settings_toml(&package_root)?.unwrap(), - &Relativity::Parent, - &NoOpProcessor, - )?); + let pyproject_config = PyprojectConfig::new( + PyprojectDiscoveryStrategy::Hierarchical, + resolve_settings_with_processor( + &find_settings_toml(&package_root)?.unwrap(), + &Relativity::Parent, + &NoOpProcessor, + )?, + None, + ); // src/app.py should not be excluded even if it lives in a hierarchy that should // be excluded by virtue of the pyproject.toml having `resources/*` in // it. assert!(!is_file_excluded( &package_root.join("src/app.py"), &resolver, - &ppd, + &pyproject_config, )); // However, resources/ignored.py should be ignored, since that `resources` is // beneath the package root. assert!(is_file_excluded( &package_root.join("resources/ignored.py"), &resolver, - &ppd, + &pyproject_config, )); Ok(()) } diff --git a/crates/ruff/src/settings/mod.rs b/crates/ruff/src/settings/mod.rs index e4110aa979ec4..4f83bb43491d3 100644 --- a/crates/ruff/src/settings/mod.rs +++ b/crates/ruff/src/settings/mod.rs @@ -13,7 +13,6 @@ use strum::IntoEnumIterator; use ruff_cache::cache_dir; use ruff_macros::CacheKey; -use crate::fs; use crate::registry::{Rule, RuleNamespace, RuleSet, INCOMPATIBLE_CODES}; use crate::rule_selector::{RuleSelector, Specificity}; use crate::rules::{ @@ -44,15 +43,10 @@ const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); pub struct AllSettings { pub cli: CliSettings, pub lib: Settings, - settings_path: Option, } impl AllSettings { - pub fn from_configuration( - config: Configuration, - project_root: &Path, - settings_path: Option, - ) -> Result { + pub fn from_configuration(config: Configuration, project_root: &Path) -> Result { Ok(Self { cli: CliSettings { cache_dir: config @@ -66,13 +60,8 @@ impl AllSettings { update_check: config.update_check.unwrap_or_default(), }, lib: Settings::from_configuration(config, project_root)?, - settings_path: settings_path.map(fs::normalize_path), }) } - - pub fn settings_path(&self) -> Option<&Path> { - self.settings_path.as_deref() - } } #[derive(Debug, Default, Clone)] diff --git a/crates/ruff_cli/src/commands/add_noqa.rs b/crates/ruff_cli/src/commands/add_noqa.rs index 40aab27fc0ed8..b73d676d6b7de 100644 --- a/crates/ruff_cli/src/commands/add_noqa.rs +++ b/crates/ruff_cli/src/commands/add_noqa.rs @@ -7,7 +7,7 @@ use log::{debug, error}; use rayon::prelude::*; use ruff::linter::add_noqa_to_path; -use ruff::resolver::PyprojectDiscovery; +use ruff::resolver::PyprojectConfig; use ruff::{packaging, resolver, warn_user_once}; use crate::args::Overrides; @@ -15,12 +15,12 @@ use crate::args::Overrides; /// Add `noqa` directives to a collection of files. pub fn add_noqa( files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, overrides: &Overrides, ) -> Result { // Collect all the files to check. let start = Instant::now(); - let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_config, overrides)?; let duration = start.elapsed(); debug!("Identified files to lint in: {:?}", duration); @@ -37,7 +37,7 @@ pub fn add_noqa( .map(ignore::DirEntry::path) .collect::>(), &resolver, - pyproject_strategy, + pyproject_config, ); let start = Instant::now(); @@ -50,7 +50,7 @@ pub fn add_noqa( .parent() .and_then(|parent| package_roots.get(parent)) .and_then(|package| *package); - let settings = resolver.resolve(path, pyproject_strategy); + let settings = resolver.resolve(path, pyproject_config); match add_noqa_to_path(path, package, settings) { Ok(count) => Some(count), Err(e) => { diff --git a/crates/ruff_cli/src/commands/run.rs b/crates/ruff_cli/src/commands/run.rs index 320e8523e1bc9..6eea564bc19a1 100644 --- a/crates/ruff_cli/src/commands/run.rs +++ b/crates/ruff_cli/src/commands/run.rs @@ -12,7 +12,7 @@ use ruff_text_size::{TextRange, TextSize}; use ruff::message::Message; use ruff::registry::Rule; -use ruff::resolver::PyprojectDiscovery; +use ruff::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; use ruff::settings::{flags, AllSettings}; use ruff::{fs, packaging, resolver, warn_user_once, IOError}; use ruff_diagnostics::Diagnostic; @@ -27,7 +27,7 @@ use crate::panic::catch_unwind; /// Run the linter over a collection of files. pub fn run( files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, overrides: &Overrides, cache: flags::Cache, noqa: flags::Noqa, @@ -35,7 +35,7 @@ pub fn run( ) -> Result { // Collect all the Python files to check. let start = Instant::now(); - let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_config, overrides)?; let duration = start.elapsed(); debug!("Identified files to lint in: {:?}", duration); @@ -52,12 +52,12 @@ pub fn run( } } - match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => { - init_cache(&settings.cli.cache_dir); + match pyproject_config.strategy { + PyprojectDiscoveryStrategy::Fixed => { + init_cache(&pyproject_config.settings.cli.cache_dir); } - PyprojectDiscovery::Hierarchical(default) => { - for settings in std::iter::once(default).chain(resolver.iter()) { + PyprojectDiscoveryStrategy::Hierarchical => { + for settings in std::iter::once(&pyproject_config.settings).chain(resolver.iter()) { init_cache(&settings.cli.cache_dir); } } @@ -72,7 +72,7 @@ pub fn run( .map(ignore::DirEntry::path) .collect::>(), &resolver, - pyproject_strategy, + pyproject_config, ); let start = Instant::now(); @@ -86,7 +86,7 @@ pub fn run( .parent() .and_then(|parent| package_roots.get(parent)) .and_then(|package| *package); - let settings = resolver.resolve_all(path, pyproject_strategy); + let settings = resolver.resolve_all(path, pyproject_config); lint_path(path, package, settings, cache, noqa, autofix).map_err(|e| { (Some(path.to_owned()), { @@ -116,7 +116,7 @@ pub fn run( fs::relativize_path(path).bold(), ":".bold() ); - let settings = resolver.resolve(path, pyproject_strategy); + let settings = resolver.resolve(path, pyproject_config); if settings.rules.enabled(Rule::IOError) { let file = SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish(); @@ -196,7 +196,7 @@ mod test { use path_absolutize::Absolutize; use ruff::logging::LogLevel; - use ruff::resolver::PyprojectDiscovery; + use ruff::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy}; use ruff::settings::configuration::{Configuration, RuleSelection}; use ruff::settings::flags::FixMode; use ruff::settings::flags::{Cache, Noqa}; @@ -238,11 +238,11 @@ mod test { let diagnostics = run( &[root_path.join("valid.ipynb")], - &PyprojectDiscovery::Fixed(AllSettings::from_configuration( - configuration, - &root_path, + &PyprojectConfig::new( + PyprojectDiscoveryStrategy::Fixed, + AllSettings::from_configuration(configuration, &root_path)?, None, - )?), + ), &overrides, Cache::Disabled, Noqa::Enabled, diff --git a/crates/ruff_cli/src/commands/run_stdin.rs b/crates/ruff_cli/src/commands/run_stdin.rs index 3ca32e2f5ff33..b5d06d7da3fa2 100644 --- a/crates/ruff_cli/src/commands/run_stdin.rs +++ b/crates/ruff_cli/src/commands/run_stdin.rs @@ -3,7 +3,7 @@ use std::path::Path; use anyhow::Result; -use ruff::resolver::PyprojectDiscovery; +use ruff::resolver::PyprojectConfig; use ruff::settings::flags; use ruff::{packaging, resolver}; @@ -20,22 +20,28 @@ fn read_from_stdin() -> Result { /// Run the linter over a single file, read from `stdin`. pub fn run_stdin( filename: Option<&Path>, - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, overrides: &Overrides, noqa: flags::Noqa, autofix: flags::FixMode, ) -> Result { if let Some(filename) = filename { - if !resolver::python_file_at_path(filename, pyproject_strategy, overrides)? { + if !resolver::python_file_at_path(filename, pyproject_config, overrides)? { return Ok(Diagnostics::default()); } } - let settings = pyproject_strategy.top_level_settings(); - let package_root = filename - .and_then(Path::parent) - .and_then(|path| packaging::detect_package_root(path, &settings.lib.namespace_packages)); + let package_root = filename.and_then(Path::parent).and_then(|path| { + packaging::detect_package_root(path, &pyproject_config.settings.lib.namespace_packages) + }); let stdin = read_from_stdin()?; - let mut diagnostics = lint_stdin(filename, package_root, &stdin, &settings.lib, noqa, autofix)?; + let mut diagnostics = lint_stdin( + filename, + package_root, + &stdin, + &pyproject_config.settings.lib, + noqa, + autofix, + )?; diagnostics.messages.sort_unstable(); Ok(diagnostics) } diff --git a/crates/ruff_cli/src/commands/show_files.rs b/crates/ruff_cli/src/commands/show_files.rs index 93e4ca4946b2b..4efdae3ca99f0 100644 --- a/crates/ruff_cli/src/commands/show_files.rs +++ b/crates/ruff_cli/src/commands/show_files.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use anyhow::Result; use itertools::Itertools; -use ruff::resolver::PyprojectDiscovery; +use ruff::resolver::PyprojectConfig; use ruff::{resolver, warn_user_once}; use crate::args::Overrides; @@ -12,11 +12,11 @@ use crate::args::Overrides; /// Show the list of files to be checked based on current settings. pub fn show_files( files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, overrides: &Overrides, ) -> Result<()> { // Collect all files in the hierarchy. - let (paths, _resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + let (paths, _resolver) = resolver::python_files_in_path(files, pyproject_config, overrides)?; if paths.is_empty() { warn_user_once!("No Python files found under the given path(s)"); diff --git a/crates/ruff_cli/src/commands/show_settings.rs b/crates/ruff_cli/src/commands/show_settings.rs index be18d0921b684..f0e152c3833a4 100644 --- a/crates/ruff_cli/src/commands/show_settings.rs +++ b/crates/ruff_cli/src/commands/show_settings.rs @@ -5,18 +5,18 @@ use anyhow::{bail, Result}; use itertools::Itertools; use ruff::resolver; -use ruff::resolver::PyprojectDiscovery; +use ruff::resolver::PyprojectConfig; use crate::args::Overrides; /// Print the user-facing configuration settings. pub fn show_settings( files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, + pyproject_config: &PyprojectConfig, overrides: &Overrides, ) -> Result<()> { // Collect all files in the hierarchy. - let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_config, overrides)?; // Print the list of files. let Some(entry) = paths @@ -26,11 +26,11 @@ pub fn show_settings( bail!("No files found under the given path"); }; let path = entry.path(); - let settings = resolver.resolve(path, pyproject_strategy); + let settings = resolver.resolve(path, pyproject_config); let mut stdout = BufWriter::new(io::stdout().lock()); writeln!(stdout, "Resolved settings for: {path:?}")?; - if let Some(settings_path) = pyproject_strategy.top_level_settings().settings_path() { + if let Some(settings_path) = pyproject_config.path.as_ref() { writeln!(stdout, "Settings path: {settings_path:?}")?; } writeln!(stdout, "{settings:#?}")?; diff --git a/crates/ruff_cli/src/lib.rs b/crates/ruff_cli/src/lib.rs index a44ff3edb0092..2a0ef4ae42d88 100644 --- a/crates/ruff_cli/src/lib.rs +++ b/crates/ruff_cli/src/lib.rs @@ -97,7 +97,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { // Construct the "default" settings. These are used when no `pyproject.toml` // files are present, or files are injected from outside of the hierarchy. - let pyproject_strategy = resolve::resolve( + let pyproject_config = resolve::resolve( cli.isolated, cli.config.as_deref(), &overrides, @@ -105,16 +105,14 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { )?; if cli.show_settings { - commands::show_settings::show_settings(&cli.files, &pyproject_strategy, &overrides)?; + commands::show_settings::show_settings(&cli.files, &pyproject_config, &overrides)?; return Ok(ExitStatus::Success); } if cli.show_files { - commands::show_files::show_files(&cli.files, &pyproject_strategy, &overrides)?; + commands::show_files::show_files(&cli.files, &pyproject_config, &overrides)?; return Ok(ExitStatus::Success); } - let top_level_settings = pyproject_strategy.top_level_settings(); - // Extract options that are included in `Settings`, but only apply at the top // level. let CliSettings { @@ -124,7 +122,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { show_fixes, update_check, .. - } = top_level_settings.cli.clone(); + } = pyproject_config.settings.cli; // Autofix rules are as follows: // - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or @@ -155,7 +153,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { printer_flags |= PrinterFlags::SHOW_FIXES; } - if top_level_settings.lib.show_source { + if pyproject_config.settings.lib.show_source { printer_flags |= PrinterFlags::SHOW_SOURCE; } @@ -171,7 +169,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { warn_user_once!("--fix is incompatible with --add-noqa."); } let modifications = - commands::add_noqa::add_noqa(&cli.files, &pyproject_strategy, &overrides)?; + commands::add_noqa::add_noqa(&cli.files, &pyproject_config, &overrides)?; if modifications > 0 && log_level >= LogLevel::Default { let s = if modifications == 1 { "" } else { "s" }; #[allow(clippy::print_stderr)] @@ -195,7 +193,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { let messages = commands::run::run( &cli.files, - &pyproject_strategy, + &pyproject_config, &overrides, cache.into(), noqa.into(), @@ -225,7 +223,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { let messages = commands::run::run( &cli.files, - &pyproject_strategy, + &pyproject_config, &overrides, cache.into(), noqa.into(), @@ -244,7 +242,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { let diagnostics = if is_stdin { commands::run_stdin::run_stdin( cli.stdin_filename.map(fs::normalize_path).as_deref(), - &pyproject_strategy, + &pyproject_config, &overrides, noqa.into(), autofix, @@ -252,7 +250,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result { } else { commands::run::run( &cli.files, - &pyproject_strategy, + &pyproject_config, &overrides, cache.into(), noqa.into(), diff --git a/crates/ruff_cli/src/resolve.rs b/crates/ruff_cli/src/resolve.rs index 4a175db15e816..0bf5db66708f3 100644 --- a/crates/ruff_cli/src/resolve.rs +++ b/crates/ruff_cli/src/resolve.rs @@ -4,7 +4,8 @@ use anyhow::Result; use path_absolutize::path_dedot; use ruff::resolver::{ - resolve_settings_with_processor, ConfigProcessor, PyprojectDiscovery, Relativity, + resolve_settings_with_processor, ConfigProcessor, PyprojectConfig, PyprojectDiscoveryStrategy, + Relativity, }; use ruff::settings::configuration::Configuration; use ruff::settings::{pyproject, AllSettings}; @@ -18,13 +19,17 @@ pub fn resolve( config: Option<&Path>, overrides: &Overrides, stdin_filename: Option<&Path>, -) -> Result { +) -> Result { // First priority: if we're running in isolated mode, use the default settings. if isolated { let mut config = Configuration::default(); overrides.process_config(&mut config); - let settings = AllSettings::from_configuration(config, &path_dedot::CWD, None)?; - return Ok(PyprojectDiscovery::Fixed(settings)); + let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?; + return Ok(PyprojectConfig::new( + PyprojectDiscoveryStrategy::Fixed, + settings, + None, + )); } // Second priority: the user specified a `pyproject.toml` file. Use that @@ -36,7 +41,11 @@ pub fn resolve( .transpose()? { let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?; - return Ok(PyprojectDiscovery::Fixed(settings)); + return Ok(PyprojectConfig::new( + PyprojectDiscoveryStrategy::Fixed, + settings, + Some(pyproject), + )); } // Third priority: find a `pyproject.toml` file in either an ancestor of @@ -50,7 +59,11 @@ pub fn resolve( .unwrap_or(&path_dedot::CWD.as_path()), )? { let settings = resolve_settings_with_processor(&pyproject, &Relativity::Parent, overrides)?; - return Ok(PyprojectDiscovery::Hierarchical(settings)); + return Ok(PyprojectConfig::new( + PyprojectDiscoveryStrategy::Hierarchical, + settings, + Some(pyproject), + )); } // Fourth priority: find a user-specific `pyproject.toml`, but resolve all paths @@ -59,7 +72,11 @@ pub fn resolve( // these act as the "default" settings.) if let Some(pyproject) = pyproject::find_user_settings_toml() { let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?; - return Ok(PyprojectDiscovery::Hierarchical(settings)); + return Ok(PyprojectConfig::new( + PyprojectDiscoveryStrategy::Hierarchical, + settings, + Some(pyproject), + )); } // Fallback: load Ruff's default settings, and resolve all paths relative to the @@ -68,6 +85,10 @@ pub fn resolve( // as the "default" settings.) let mut config = Configuration::default(); overrides.process_config(&mut config); - let settings = AllSettings::from_configuration(config, &path_dedot::CWD, None)?; - Ok(PyprojectDiscovery::Hierarchical(settings)) + let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?; + Ok(PyprojectConfig::new( + PyprojectDiscoveryStrategy::Hierarchical, + settings, + None, + )) }