Show settings path in --show-settings output (#4199)

This commit is contained in:
Dhruv Manilawala 2023-05-04 11:52:31 +05:30 committed by GitHub
parent 37aae666c7
commit 59d40f9f81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 152 additions and 102 deletions

View file

@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::resolver::{PyprojectDiscovery, Resolver}; use crate::resolver::{PyprojectConfig, Resolver};
// If we have a Python package layout like: // If we have a Python package layout like:
// - root/ // - root/
@ -82,7 +82,7 @@ fn detect_package_root_with_cache<'a>(
pub fn detect_package_roots<'a>( pub fn detect_package_roots<'a>(
files: &[&'a Path], files: &[&'a Path],
resolver: &'a Resolver, resolver: &'a Resolver,
pyproject_strategy: &'a PyprojectDiscovery, pyproject_config: &'a PyprojectConfig,
) -> FxHashMap<&'a Path, Option<&'a Path>> { ) -> FxHashMap<&'a Path, Option<&'a Path>> {
// Pre-populate the module cache, since the list of files could (but isn't // Pre-populate the module cache, since the list of files could (but isn't
// required to) contain some `__init__.py` files. // 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. // Search for the package root for each file.
let mut package_roots: FxHashMap<&Path, Option<&Path>> = FxHashMap::default(); let mut package_roots: FxHashMap<&Path, Option<&Path>> = FxHashMap::default();
for file in files { for file in files {
let namespace_packages = &resolver let namespace_packages = &resolver.resolve(file, pyproject_config).namespace_packages;
.resolve(file, pyproject_strategy)
.namespace_packages;
if let Some(package) = file.parent() { if let Some(package) = file.parent() {
if package_roots.contains_key(package) { if package_roots.contains_key(package) {
continue; continue;

View file

@ -17,25 +17,42 @@ use crate::settings::configuration::Configuration;
use crate::settings::pyproject::settings_toml; use crate::settings::pyproject::settings_toml;
use crate::settings::{pyproject, AllSettings, Settings}; 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<PathBuf>,
}
impl PyprojectConfig {
pub fn new(
strategy: PyprojectDiscoveryStrategy,
settings: AllSettings,
path: Option<PathBuf>,
) -> Self {
Self {
strategy,
settings,
path: path.map(fs::normalize_path),
}
}
}
/// The strategy used to discover the relevant `pyproject.toml` file for each /// The strategy used to discover the relevant `pyproject.toml` file for each
/// Python file. /// Python file.
#[derive(Debug, is_macro::Is)] #[derive(Debug, is_macro::Is)]
pub enum PyprojectDiscovery { pub enum PyprojectDiscoveryStrategy {
/// Use a fixed `pyproject.toml` file for all Python files (i.e., one /// Use a fixed `pyproject.toml` file for all Python files (i.e., one
/// provided on the command-line). /// provided on the command-line).
Fixed(AllSettings), Fixed,
/// Use the closest `pyproject.toml` file in the filesystem hierarchy, or /// Use the closest `pyproject.toml` file in the filesystem hierarchy, or
/// the default settings. /// the default settings.
Hierarchical(AllSettings), Hierarchical,
}
impl PyprojectDiscovery {
pub fn top_level_settings(&self) -> &AllSettings {
match self {
PyprojectDiscovery::Fixed(settings) => settings,
PyprojectDiscovery::Hierarchical(settings) => settings,
}
}
} }
/// The strategy for resolving file paths in a `pyproject.toml`. /// The strategy for resolving file paths in a `pyproject.toml`.
@ -75,21 +92,25 @@ impl Resolver {
pub fn resolve_all<'a>( pub fn resolve_all<'a>(
&'a self, &'a self,
path: &Path, path: &Path,
strategy: &'a PyprojectDiscovery, pyproject_config: &'a PyprojectConfig,
) -> &'a AllSettings { ) -> &'a AllSettings {
match strategy { match pyproject_config.strategy {
PyprojectDiscovery::Fixed(settings) => settings, PyprojectDiscoveryStrategy::Fixed => &pyproject_config.settings,
PyprojectDiscovery::Hierarchical(default) => self PyprojectDiscoveryStrategy::Hierarchical => self
.settings .settings
.iter() .iter()
.rev() .rev()
.find_map(|(root, settings)| path.starts_with(root).then_some(settings)) .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 { pub fn resolve<'a>(
&self.resolve_all(path, strategy).lib &'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`]. /// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
@ -200,7 +221,7 @@ fn match_exclusion<P: AsRef<Path>, R: AsRef<Path>>(
/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths. /// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
pub fn python_files_in_path( pub fn python_files_in_path(
paths: &[PathBuf], paths: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
processor: impl ConfigProcessor, processor: impl ConfigProcessor,
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> { ) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
// Normalize every path (e.g., convert from relative to absolute). // Normalize every path (e.g., convert from relative to absolute).
@ -209,7 +230,7 @@ pub fn python_files_in_path(
// Search for `pyproject.toml` files in all parent directories. // Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default(); let mut resolver = Resolver::default();
let mut seen = FxHashSet::default(); let mut seen = FxHashSet::default();
if pyproject_strategy.is_hierarchical() { if pyproject_config.strategy.is_hierarchical() {
for path in &paths { for path in &paths {
for ancestor in path.ancestors() { for ancestor in path.ancestors() {
if seen.insert(ancestor) { if seen.insert(ancestor) {
@ -224,8 +245,8 @@ pub fn python_files_in_path(
} }
// Check if the paths themselves are excluded. // Check if the paths themselves are excluded.
if pyproject_strategy.top_level_settings().lib.force_exclude { if pyproject_config.settings.lib.force_exclude {
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy)); paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_config));
if paths.is_empty() { if paths.is_empty() {
return Ok((vec![], resolver)); return Ok((vec![], resolver));
} }
@ -240,12 +261,7 @@ pub fn python_files_in_path(
for path in &paths[1..] { for path in &paths[1..] {
builder.add(path); builder.add(path);
} }
builder.standard_filters( builder.standard_filters(pyproject_config.settings.lib.respect_gitignore);
pyproject_strategy
.top_level_settings()
.lib
.respect_gitignore,
);
builder.hidden(false); builder.hidden(false);
let walker = builder.build_parallel(); let walker = builder.build_parallel();
@ -261,7 +277,7 @@ pub fn python_files_in_path(
if entry.depth() > 0 { if entry.depth() > 0 {
let path = entry.path(); let path = entry.path();
let resolver = resolver.read().unwrap(); 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 let Some(file_name) = path.file_name() {
if !settings.exclude.is_empty() if !settings.exclude.is_empty()
&& match_exclusion(path, file_name, &settings.exclude) && match_exclusion(path, file_name, &settings.exclude)
@ -283,7 +299,7 @@ pub fn python_files_in_path(
// Search for the `pyproject.toml` file in this directory, before we visit any // Search for the `pyproject.toml` file in this directory, before we visit any
// of its contents. // of its contents.
if pyproject_strategy.is_hierarchical() { if pyproject_config.strategy.is_hierarchical() {
if let Ok(entry) = &result { if let Ok(entry) = &result {
if entry if entry
.file_type() .file_type()
@ -321,7 +337,7 @@ pub fn python_files_in_path(
// Otherwise, check if the file is included. // Otherwise, check if the file is included.
let path = entry.path(); let path = entry.path();
let resolver = resolver.read().unwrap(); 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) { if settings.include.is_match(path) {
debug!("Included path via `include`: {:?}", path); debug!("Included path via `include`: {:?}", path);
true true
@ -348,10 +364,10 @@ pub fn python_files_in_path(
/// Return `true` if the Python file at [`Path`] is _not_ excluded. /// Return `true` if the Python file at [`Path`] is _not_ excluded.
pub fn python_file_at_path( pub fn python_file_at_path(
path: &Path, path: &Path,
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
processor: impl ConfigProcessor, processor: impl ConfigProcessor,
) -> Result<bool> { ) -> Result<bool> {
if !pyproject_strategy.top_level_settings().lib.force_exclude { if !pyproject_config.settings.lib.force_exclude {
return Ok(true); return Ok(true);
} }
@ -360,7 +376,7 @@ pub fn python_file_at_path(
// Search for `pyproject.toml` files in all parent directories. // Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default(); let mut resolver = Resolver::default();
if pyproject_strategy.is_hierarchical() { if pyproject_config.strategy.is_hierarchical() {
for ancestor in path.ancestors() { for ancestor in path.ancestors() {
if let Some(pyproject) = settings_toml(ancestor)? { if let Some(pyproject) = settings_toml(ancestor)? {
let (root, settings) = let (root, settings) =
@ -371,14 +387,14 @@ pub fn python_file_at_path(
} }
// Check exclusions. // 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. /// Return `true` if the given top-level [`Path`] should be excluded.
fn is_file_excluded( fn is_file_excluded(
path: &Path, path: &Path,
resolver: &Resolver, resolver: &Resolver,
pyproject_strategy: &PyprojectDiscovery, pyproject_strategy: &PyprojectConfig,
) -> bool { ) -> bool {
// TODO(charlie): Respect gitignore. // TODO(charlie): Respect gitignore.
for path in path.ancestors() { for path in path.ancestors() {
@ -419,7 +435,7 @@ mod tests {
use crate::resolver::{ use crate::resolver::{
is_file_excluded, match_exclusion, resolve_settings_with_processor, NoOpProcessor, 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::pyproject::find_settings_toml;
use crate::settings::types::FilePattern; use crate::settings::types::FilePattern;
@ -560,25 +576,29 @@ mod tests {
fn rooted_exclusion() -> Result<()> { fn rooted_exclusion() -> Result<()> {
let package_root = test_resource_path("package"); let package_root = test_resource_path("package");
let resolver = Resolver::default(); let resolver = Resolver::default();
let ppd = PyprojectDiscovery::Hierarchical(resolve_settings_with_processor( let pyproject_config = PyprojectConfig::new(
&find_settings_toml(&package_root)?.unwrap(), PyprojectDiscoveryStrategy::Hierarchical,
&Relativity::Parent, resolve_settings_with_processor(
&NoOpProcessor, &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 // 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 // be excluded by virtue of the pyproject.toml having `resources/*` in
// it. // it.
assert!(!is_file_excluded( assert!(!is_file_excluded(
&package_root.join("src/app.py"), &package_root.join("src/app.py"),
&resolver, &resolver,
&ppd, &pyproject_config,
)); ));
// However, resources/ignored.py should be ignored, since that `resources` is // However, resources/ignored.py should be ignored, since that `resources` is
// beneath the package root. // beneath the package root.
assert!(is_file_excluded( assert!(is_file_excluded(
&package_root.join("resources/ignored.py"), &package_root.join("resources/ignored.py"),
&resolver, &resolver,
&ppd, &pyproject_config,
)); ));
Ok(()) Ok(())
} }

View file

@ -7,7 +7,7 @@ use log::{debug, error};
use rayon::prelude::*; use rayon::prelude::*;
use ruff::linter::add_noqa_to_path; use ruff::linter::add_noqa_to_path;
use ruff::resolver::PyprojectDiscovery; use ruff::resolver::PyprojectConfig;
use ruff::{packaging, resolver, warn_user_once}; use ruff::{packaging, resolver, warn_user_once};
use crate::args::Overrides; use crate::args::Overrides;
@ -15,12 +15,12 @@ use crate::args::Overrides;
/// Add `noqa` directives to a collection of files. /// Add `noqa` directives to a collection of files.
pub fn add_noqa( pub fn add_noqa(
files: &[PathBuf], files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
overrides: &Overrides, overrides: &Overrides,
) -> Result<usize> { ) -> Result<usize> {
// Collect all the files to check. // Collect all the files to check.
let start = Instant::now(); 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(); let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration); debug!("Identified files to lint in: {:?}", duration);
@ -37,7 +37,7 @@ pub fn add_noqa(
.map(ignore::DirEntry::path) .map(ignore::DirEntry::path)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&resolver, &resolver,
pyproject_strategy, pyproject_config,
); );
let start = Instant::now(); let start = Instant::now();
@ -50,7 +50,7 @@ pub fn add_noqa(
.parent() .parent()
.and_then(|parent| package_roots.get(parent)) .and_then(|parent| package_roots.get(parent))
.and_then(|package| *package); .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) { match add_noqa_to_path(path, package, settings) {
Ok(count) => Some(count), Ok(count) => Some(count),
Err(e) => { Err(e) => {

View file

@ -12,7 +12,7 @@ use ruff_text_size::{TextRange, TextSize};
use ruff::message::Message; use ruff::message::Message;
use ruff::registry::Rule; use ruff::registry::Rule;
use ruff::resolver::PyprojectDiscovery; use ruff::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
use ruff::settings::{flags, AllSettings}; use ruff::settings::{flags, AllSettings};
use ruff::{fs, packaging, resolver, warn_user_once, IOError}; use ruff::{fs, packaging, resolver, warn_user_once, IOError};
use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Diagnostic;
@ -27,7 +27,7 @@ use crate::panic::catch_unwind;
/// Run the linter over a collection of files. /// Run the linter over a collection of files.
pub fn run( pub fn run(
files: &[PathBuf], files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
overrides: &Overrides, overrides: &Overrides,
cache: flags::Cache, cache: flags::Cache,
noqa: flags::Noqa, noqa: flags::Noqa,
@ -35,7 +35,7 @@ pub fn run(
) -> Result<Diagnostics> { ) -> Result<Diagnostics> {
// Collect all the Python files to check. // Collect all the Python files to check.
let start = Instant::now(); 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(); let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration); debug!("Identified files to lint in: {:?}", duration);
@ -52,12 +52,12 @@ pub fn run(
} }
} }
match &pyproject_strategy { match pyproject_config.strategy {
PyprojectDiscovery::Fixed(settings) => { PyprojectDiscoveryStrategy::Fixed => {
init_cache(&settings.cli.cache_dir); init_cache(&pyproject_config.settings.cli.cache_dir);
} }
PyprojectDiscovery::Hierarchical(default) => { PyprojectDiscoveryStrategy::Hierarchical => {
for settings in std::iter::once(default).chain(resolver.iter()) { for settings in std::iter::once(&pyproject_config.settings).chain(resolver.iter()) {
init_cache(&settings.cli.cache_dir); init_cache(&settings.cli.cache_dir);
} }
} }
@ -72,7 +72,7 @@ pub fn run(
.map(ignore::DirEntry::path) .map(ignore::DirEntry::path)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&resolver, &resolver,
pyproject_strategy, pyproject_config,
); );
let start = Instant::now(); let start = Instant::now();
@ -86,7 +86,7 @@ pub fn run(
.parent() .parent()
.and_then(|parent| package_roots.get(parent)) .and_then(|parent| package_roots.get(parent))
.and_then(|package| *package); .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| { lint_path(path, package, settings, cache, noqa, autofix).map_err(|e| {
(Some(path.to_owned()), { (Some(path.to_owned()), {
@ -116,7 +116,7 @@ pub fn run(
fs::relativize_path(path).bold(), fs::relativize_path(path).bold(),
":".bold() ":".bold()
); );
let settings = resolver.resolve(path, pyproject_strategy); let settings = resolver.resolve(path, pyproject_config);
if settings.rules.enabled(Rule::IOError) { if settings.rules.enabled(Rule::IOError) {
let file = let file =
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish(); SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
@ -196,7 +196,7 @@ mod test {
use path_absolutize::Absolutize; use path_absolutize::Absolutize;
use ruff::logging::LogLevel; use ruff::logging::LogLevel;
use ruff::resolver::PyprojectDiscovery; use ruff::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
use ruff::settings::configuration::{Configuration, RuleSelection}; use ruff::settings::configuration::{Configuration, RuleSelection};
use ruff::settings::flags::FixMode; use ruff::settings::flags::FixMode;
use ruff::settings::flags::{Cache, Noqa}; use ruff::settings::flags::{Cache, Noqa};
@ -238,7 +238,11 @@ mod test {
let diagnostics = run( let diagnostics = run(
&[root_path.join("valid.ipynb")], &[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, &overrides,
Cache::Disabled, Cache::Disabled,
Noqa::Enabled, Noqa::Enabled,

View file

@ -3,7 +3,7 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use ruff::resolver::PyprojectDiscovery; use ruff::resolver::PyprojectConfig;
use ruff::settings::flags; use ruff::settings::flags;
use ruff::{packaging, resolver}; use ruff::{packaging, resolver};
@ -20,22 +20,28 @@ fn read_from_stdin() -> Result<String> {
/// Run the linter over a single file, read from `stdin`. /// Run the linter over a single file, read from `stdin`.
pub fn run_stdin( pub fn run_stdin(
filename: Option<&Path>, filename: Option<&Path>,
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
overrides: &Overrides, overrides: &Overrides,
noqa: flags::Noqa, noqa: flags::Noqa,
autofix: flags::FixMode, autofix: flags::FixMode,
) -> Result<Diagnostics> { ) -> Result<Diagnostics> {
if let Some(filename) = filename { 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()); return Ok(Diagnostics::default());
} }
} }
let settings = pyproject_strategy.top_level_settings(); let package_root = filename.and_then(Path::parent).and_then(|path| {
let package_root = filename packaging::detect_package_root(path, &pyproject_config.settings.lib.namespace_packages)
.and_then(Path::parent) });
.and_then(|path| packaging::detect_package_root(path, &settings.lib.namespace_packages));
let stdin = read_from_stdin()?; 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(); diagnostics.messages.sort_unstable();
Ok(diagnostics) Ok(diagnostics)
} }

View file

@ -4,7 +4,7 @@ use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use itertools::Itertools; use itertools::Itertools;
use ruff::resolver::PyprojectDiscovery; use ruff::resolver::PyprojectConfig;
use ruff::{resolver, warn_user_once}; use ruff::{resolver, warn_user_once};
use crate::args::Overrides; use crate::args::Overrides;
@ -12,11 +12,11 @@ use crate::args::Overrides;
/// Show the list of files to be checked based on current settings. /// Show the list of files to be checked based on current settings.
pub fn show_files( pub fn show_files(
files: &[PathBuf], files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
overrides: &Overrides, overrides: &Overrides,
) -> Result<()> { ) -> Result<()> {
// Collect all files in the hierarchy. // 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() { if paths.is_empty() {
warn_user_once!("No Python files found under the given path(s)"); warn_user_once!("No Python files found under the given path(s)");

View file

@ -5,18 +5,18 @@ use anyhow::{bail, Result};
use itertools::Itertools; use itertools::Itertools;
use ruff::resolver; use ruff::resolver;
use ruff::resolver::PyprojectDiscovery; use ruff::resolver::PyprojectConfig;
use crate::args::Overrides; use crate::args::Overrides;
/// Print the user-facing configuration settings. /// Print the user-facing configuration settings.
pub fn show_settings( pub fn show_settings(
files: &[PathBuf], files: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery, pyproject_config: &PyprojectConfig,
overrides: &Overrides, overrides: &Overrides,
) -> Result<()> { ) -> Result<()> {
// Collect all files in the hierarchy. // 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. // Print the list of files.
let Some(entry) = paths let Some(entry) = paths
@ -26,10 +26,13 @@ pub fn show_settings(
bail!("No files found under the given path"); bail!("No files found under the given path");
}; };
let path = entry.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()); let mut stdout = BufWriter::new(io::stdout().lock());
writeln!(stdout, "Resolved settings for: {path:?}")?; writeln!(stdout, "Resolved settings for: {path:?}")?;
if let Some(settings_path) = pyproject_config.path.as_ref() {
writeln!(stdout, "Settings path: {settings_path:?}")?;
}
writeln!(stdout, "{settings:#?}")?; writeln!(stdout, "{settings:#?}")?;
Ok(()) Ok(())

View file

@ -97,7 +97,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
// Construct the "default" settings. These are used when no `pyproject.toml` // Construct the "default" settings. These are used when no `pyproject.toml`
// files are present, or files are injected from outside of the hierarchy. // 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.isolated,
cli.config.as_deref(), cli.config.as_deref(),
&overrides, &overrides,
@ -105,16 +105,14 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
)?; )?;
if cli.show_settings { 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); return Ok(ExitStatus::Success);
} }
if cli.show_files { 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); 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 // Extract options that are included in `Settings`, but only apply at the top
// level. // level.
let CliSettings { let CliSettings {
@ -124,7 +122,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
show_fixes, show_fixes,
update_check, update_check,
.. ..
} = top_level_settings.cli.clone(); } = pyproject_config.settings.cli;
// Autofix rules are as follows: // Autofix rules are as follows:
// - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or // - 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<ExitStatus> {
printer_flags |= PrinterFlags::SHOW_FIXES; printer_flags |= PrinterFlags::SHOW_FIXES;
} }
if top_level_settings.lib.show_source { if pyproject_config.settings.lib.show_source {
printer_flags |= PrinterFlags::SHOW_SOURCE; printer_flags |= PrinterFlags::SHOW_SOURCE;
} }
@ -171,7 +169,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
warn_user_once!("--fix is incompatible with --add-noqa."); warn_user_once!("--fix is incompatible with --add-noqa.");
} }
let modifications = 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 { if modifications > 0 && log_level >= LogLevel::Default {
let s = if modifications == 1 { "" } else { "s" }; let s = if modifications == 1 { "" } else { "s" };
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
@ -195,7 +193,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
let messages = commands::run::run( let messages = commands::run::run(
&cli.files, &cli.files,
&pyproject_strategy, &pyproject_config,
&overrides, &overrides,
cache.into(), cache.into(),
noqa.into(), noqa.into(),
@ -225,7 +223,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
let messages = commands::run::run( let messages = commands::run::run(
&cli.files, &cli.files,
&pyproject_strategy, &pyproject_config,
&overrides, &overrides,
cache.into(), cache.into(),
noqa.into(), noqa.into(),
@ -244,7 +242,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
let diagnostics = if is_stdin { let diagnostics = if is_stdin {
commands::run_stdin::run_stdin( commands::run_stdin::run_stdin(
cli.stdin_filename.map(fs::normalize_path).as_deref(), cli.stdin_filename.map(fs::normalize_path).as_deref(),
&pyproject_strategy, &pyproject_config,
&overrides, &overrides,
noqa.into(), noqa.into(),
autofix, autofix,
@ -252,7 +250,7 @@ fn check(args: CheckArgs, log_level: LogLevel) -> Result<ExitStatus> {
} else { } else {
commands::run::run( commands::run::run(
&cli.files, &cli.files,
&pyproject_strategy, &pyproject_config,
&overrides, &overrides,
cache.into(), cache.into(),
noqa.into(), noqa.into(),

View file

@ -4,7 +4,8 @@ use anyhow::Result;
use path_absolutize::path_dedot; use path_absolutize::path_dedot;
use ruff::resolver::{ 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::configuration::Configuration;
use ruff::settings::{pyproject, AllSettings}; use ruff::settings::{pyproject, AllSettings};
@ -18,13 +19,17 @@ pub fn resolve(
config: Option<&Path>, config: Option<&Path>,
overrides: &Overrides, overrides: &Overrides,
stdin_filename: Option<&Path>, stdin_filename: Option<&Path>,
) -> Result<PyprojectDiscovery> { ) -> Result<PyprojectConfig> {
// First priority: if we're running in isolated mode, use the default settings. // First priority: if we're running in isolated mode, use the default settings.
if isolated { if isolated {
let mut config = Configuration::default(); let mut config = Configuration::default();
overrides.process_config(&mut config); overrides.process_config(&mut config);
let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?; let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?;
return Ok(PyprojectDiscovery::Fixed(settings)); return Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Fixed,
settings,
None,
));
} }
// Second priority: the user specified a `pyproject.toml` file. Use that // Second priority: the user specified a `pyproject.toml` file. Use that
@ -36,7 +41,11 @@ pub fn resolve(
.transpose()? .transpose()?
{ {
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?; 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 // 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()), .unwrap_or(&path_dedot::CWD.as_path()),
)? { )? {
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Parent, overrides)?; 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 // 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.) // these act as the "default" settings.)
if let Some(pyproject) = pyproject::find_user_settings_toml() { if let Some(pyproject) = pyproject::find_user_settings_toml() {
let settings = resolve_settings_with_processor(&pyproject, &Relativity::Cwd, overrides)?; 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 // Fallback: load Ruff's default settings, and resolve all paths relative to the
@ -69,5 +86,9 @@ pub fn resolve(
let mut config = Configuration::default(); let mut config = Configuration::default();
overrides.process_config(&mut config); overrides.process_config(&mut config);
let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?; let settings = AllSettings::from_configuration(config, &path_dedot::CWD)?;
Ok(PyprojectDiscovery::Hierarchical(settings)) Ok(PyprojectConfig::new(
PyprojectDiscoveryStrategy::Hierarchical,
settings,
None,
))
} }