Skip to content

Commit 59d40f9

Browse files
authored
Show settings path in --show-settings output (#4199)
1 parent 37aae66 commit 59d40f9

File tree

9 files changed

+152
-102
lines changed

9 files changed

+152
-102
lines changed

crates/ruff/src/packaging.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
44

55
use rustc_hash::FxHashMap;
66

7-
use crate::resolver::{PyprojectDiscovery, Resolver};
7+
use crate::resolver::{PyprojectConfig, Resolver};
88

99
// If we have a Python package layout like:
1010
// - root/
@@ -82,7 +82,7 @@ fn detect_package_root_with_cache<'a>(
8282
pub fn detect_package_roots<'a>(
8383
files: &[&'a Path],
8484
resolver: &'a Resolver,
85-
pyproject_strategy: &'a PyprojectDiscovery,
85+
pyproject_config: &'a PyprojectConfig,
8686
) -> FxHashMap<&'a Path, Option<&'a Path>> {
8787
// Pre-populate the module cache, since the list of files could (but isn't
8888
// required to) contain some `__init__.py` files.
@@ -98,9 +98,7 @@ pub fn detect_package_roots<'a>(
9898
// Search for the package root for each file.
9999
let mut package_roots: FxHashMap<&Path, Option<&Path>> = FxHashMap::default();
100100
for file in files {
101-
let namespace_packages = &resolver
102-
.resolve(file, pyproject_strategy)
103-
.namespace_packages;
101+
let namespace_packages = &resolver.resolve(file, pyproject_config).namespace_packages;
104102
if let Some(package) = file.parent() {
105103
if package_roots.contains_key(package) {
106104
continue;

crates/ruff/src/resolver.rs

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,42 @@ use crate::settings::configuration::Configuration;
1717
use crate::settings::pyproject::settings_toml;
1818
use crate::settings::{pyproject, AllSettings, Settings};
1919

20+
/// The configuration information from a `pyproject.toml` file.
21+
pub struct PyprojectConfig {
22+
/// The strategy used to discover the relevant `pyproject.toml` file for
23+
/// each Python file.
24+
pub strategy: PyprojectDiscoveryStrategy,
25+
/// All settings from the `pyproject.toml` file.
26+
pub settings: AllSettings,
27+
/// Absolute path to the `pyproject.toml` file. This would be `None` when
28+
/// either using the default settings or the `--isolated` flag is set.
29+
pub path: Option<PathBuf>,
30+
}
31+
32+
impl PyprojectConfig {
33+
pub fn new(
34+
strategy: PyprojectDiscoveryStrategy,
35+
settings: AllSettings,
36+
path: Option<PathBuf>,
37+
) -> Self {
38+
Self {
39+
strategy,
40+
settings,
41+
path: path.map(fs::normalize_path),
42+
}
43+
}
44+
}
45+
2046
/// The strategy used to discover the relevant `pyproject.toml` file for each
2147
/// Python file.
2248
#[derive(Debug, is_macro::Is)]
23-
pub enum PyprojectDiscovery {
49+
pub enum PyprojectDiscoveryStrategy {
2450
/// Use a fixed `pyproject.toml` file for all Python files (i.e., one
2551
/// provided on the command-line).
26-
Fixed(AllSettings),
52+
Fixed,
2753
/// Use the closest `pyproject.toml` file in the filesystem hierarchy, or
2854
/// the default settings.
29-
Hierarchical(AllSettings),
30-
}
31-
32-
impl PyprojectDiscovery {
33-
pub fn top_level_settings(&self) -> &AllSettings {
34-
match self {
35-
PyprojectDiscovery::Fixed(settings) => settings,
36-
PyprojectDiscovery::Hierarchical(settings) => settings,
37-
}
38-
}
55+
Hierarchical,
3956
}
4057

4158
/// The strategy for resolving file paths in a `pyproject.toml`.
@@ -75,21 +92,25 @@ impl Resolver {
7592
pub fn resolve_all<'a>(
7693
&'a self,
7794
path: &Path,
78-
strategy: &'a PyprojectDiscovery,
95+
pyproject_config: &'a PyprojectConfig,
7996
) -> &'a AllSettings {
80-
match strategy {
81-
PyprojectDiscovery::Fixed(settings) => settings,
82-
PyprojectDiscovery::Hierarchical(default) => self
97+
match pyproject_config.strategy {
98+
PyprojectDiscoveryStrategy::Fixed => &pyproject_config.settings,
99+
PyprojectDiscoveryStrategy::Hierarchical => self
83100
.settings
84101
.iter()
85102
.rev()
86103
.find_map(|(root, settings)| path.starts_with(root).then_some(settings))
87-
.unwrap_or(default),
104+
.unwrap_or(&pyproject_config.settings),
88105
}
89106
}
90107

91-
pub fn resolve<'a>(&'a self, path: &Path, strategy: &'a PyprojectDiscovery) -> &'a Settings {
92-
&self.resolve_all(path, strategy).lib
108+
pub fn resolve<'a>(
109+
&'a self,
110+
path: &Path,
111+
pyproject_config: &'a PyprojectConfig,
112+
) -> &'a Settings {
113+
&self.resolve_all(path, pyproject_config).lib
93114
}
94115

95116
/// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
@@ -200,7 +221,7 @@ fn match_exclusion<P: AsRef<Path>, R: AsRef<Path>>(
200221
/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
201222
pub fn python_files_in_path(
202223
paths: &[PathBuf],
203-
pyproject_strategy: &PyprojectDiscovery,
224+
pyproject_config: &PyprojectConfig,
204225
processor: impl ConfigProcessor,
205226
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
206227
// Normalize every path (e.g., convert from relative to absolute).
@@ -209,7 +230,7 @@ pub fn python_files_in_path(
209230
// Search for `pyproject.toml` files in all parent directories.
210231
let mut resolver = Resolver::default();
211232
let mut seen = FxHashSet::default();
212-
if pyproject_strategy.is_hierarchical() {
233+
if pyproject_config.strategy.is_hierarchical() {
213234
for path in &paths {
214235
for ancestor in path.ancestors() {
215236
if seen.insert(ancestor) {
@@ -224,8 +245,8 @@ pub fn python_files_in_path(
224245
}
225246

226247
// Check if the paths themselves are excluded.
227-
if pyproject_strategy.top_level_settings().lib.force_exclude {
228-
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy));
248+
if pyproject_config.settings.lib.force_exclude {
249+
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_config));
229250
if paths.is_empty() {
230251
return Ok((vec![], resolver));
231252
}
@@ -240,12 +261,7 @@ pub fn python_files_in_path(
240261
for path in &paths[1..] {
241262
builder.add(path);
242263
}
243-
builder.standard_filters(
244-
pyproject_strategy
245-
.top_level_settings()
246-
.lib
247-
.respect_gitignore,
248-
);
264+
builder.standard_filters(pyproject_config.settings.lib.respect_gitignore);
249265
builder.hidden(false);
250266
let walker = builder.build_parallel();
251267

@@ -261,7 +277,7 @@ pub fn python_files_in_path(
261277
if entry.depth() > 0 {
262278
let path = entry.path();
263279
let resolver = resolver.read().unwrap();
264-
let settings = resolver.resolve(path, pyproject_strategy);
280+
let settings = resolver.resolve(path, pyproject_config);
265281
if let Some(file_name) = path.file_name() {
266282
if !settings.exclude.is_empty()
267283
&& match_exclusion(path, file_name, &settings.exclude)
@@ -283,7 +299,7 @@ pub fn python_files_in_path(
283299

284300
// Search for the `pyproject.toml` file in this directory, before we visit any
285301
// of its contents.
286-
if pyproject_strategy.is_hierarchical() {
302+
if pyproject_config.strategy.is_hierarchical() {
287303
if let Ok(entry) = &result {
288304
if entry
289305
.file_type()
@@ -321,7 +337,7 @@ pub fn python_files_in_path(
321337
// Otherwise, check if the file is included.
322338
let path = entry.path();
323339
let resolver = resolver.read().unwrap();
324-
let settings = resolver.resolve(path, pyproject_strategy);
340+
let settings = resolver.resolve(path, pyproject_config);
325341
if settings.include.is_match(path) {
326342
debug!("Included path via `include`: {:?}", path);
327343
true
@@ -348,10 +364,10 @@ pub fn python_files_in_path(
348364
/// Return `true` if the Python file at [`Path`] is _not_ excluded.
349365
pub fn python_file_at_path(
350366
path: &Path,
351-
pyproject_strategy: &PyprojectDiscovery,
367+
pyproject_config: &PyprojectConfig,
352368
processor: impl ConfigProcessor,
353369
) -> Result<bool> {
354-
if !pyproject_strategy.top_level_settings().lib.force_exclude {
370+
if !pyproject_config.settings.lib.force_exclude {
355371
return Ok(true);
356372
}
357373

@@ -360,7 +376,7 @@ pub fn python_file_at_path(
360376

361377
// Search for `pyproject.toml` files in all parent directories.
362378
let mut resolver = Resolver::default();
363-
if pyproject_strategy.is_hierarchical() {
379+
if pyproject_config.strategy.is_hierarchical() {
364380
for ancestor in path.ancestors() {
365381
if let Some(pyproject) = settings_toml(ancestor)? {
366382
let (root, settings) =
@@ -371,14 +387,14 @@ pub fn python_file_at_path(
371387
}
372388

373389
// Check exclusions.
374-
Ok(!is_file_excluded(&path, &resolver, pyproject_strategy))
390+
Ok(!is_file_excluded(&path, &resolver, pyproject_config))
375391
}
376392

377393
/// Return `true` if the given top-level [`Path`] should be excluded.
378394
fn is_file_excluded(
379395
path: &Path,
380396
resolver: &Resolver,
381-
pyproject_strategy: &PyprojectDiscovery,
397+
pyproject_strategy: &PyprojectConfig,
382398
) -> bool {
383399
// TODO(charlie): Respect gitignore.
384400
for path in path.ancestors() {
@@ -419,7 +435,7 @@ mod tests {
419435

420436
use crate::resolver::{
421437
is_file_excluded, match_exclusion, resolve_settings_with_processor, NoOpProcessor,
422-
PyprojectDiscovery, Relativity, Resolver,
438+
PyprojectConfig, PyprojectDiscoveryStrategy, Relativity, Resolver,
423439
};
424440
use crate::settings::pyproject::find_settings_toml;
425441
use crate::settings::types::FilePattern;
@@ -560,25 +576,29 @@ mod tests {
560576
fn rooted_exclusion() -> Result<()> {
561577
let package_root = test_resource_path("package");
562578
let resolver = Resolver::default();
563-
let ppd = PyprojectDiscovery::Hierarchical(resolve_settings_with_processor(
564-
&find_settings_toml(&package_root)?.unwrap(),
565-
&Relativity::Parent,
566-
&NoOpProcessor,
567-
)?);
579+
let pyproject_config = PyprojectConfig::new(
580+
PyprojectDiscoveryStrategy::Hierarchical,
581+
resolve_settings_with_processor(
582+
&find_settings_toml(&package_root)?.unwrap(),
583+
&Relativity::Parent,
584+
&NoOpProcessor,
585+
)?,
586+
None,
587+
);
568588
// src/app.py should not be excluded even if it lives in a hierarchy that should
569589
// be excluded by virtue of the pyproject.toml having `resources/*` in
570590
// it.
571591
assert!(!is_file_excluded(
572592
&package_root.join("src/app.py"),
573593
&resolver,
574-
&ppd,
594+
&pyproject_config,
575595
));
576596
// However, resources/ignored.py should be ignored, since that `resources` is
577597
// beneath the package root.
578598
assert!(is_file_excluded(
579599
&package_root.join("resources/ignored.py"),
580600
&resolver,
581-
&ppd,
601+
&pyproject_config,
582602
));
583603
Ok(())
584604
}

crates/ruff_cli/src/commands/add_noqa.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ use log::{debug, error};
77
use rayon::prelude::*;
88

99
use ruff::linter::add_noqa_to_path;
10-
use ruff::resolver::PyprojectDiscovery;
10+
use ruff::resolver::PyprojectConfig;
1111
use ruff::{packaging, resolver, warn_user_once};
1212

1313
use crate::args::Overrides;
1414

1515
/// Add `noqa` directives to a collection of files.
1616
pub fn add_noqa(
1717
files: &[PathBuf],
18-
pyproject_strategy: &PyprojectDiscovery,
18+
pyproject_config: &PyprojectConfig,
1919
overrides: &Overrides,
2020
) -> Result<usize> {
2121
// Collect all the files to check.
2222
let start = Instant::now();
23-
let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?;
23+
let (paths, resolver) = resolver::python_files_in_path(files, pyproject_config, overrides)?;
2424
let duration = start.elapsed();
2525
debug!("Identified files to lint in: {:?}", duration);
2626

@@ -37,7 +37,7 @@ pub fn add_noqa(
3737
.map(ignore::DirEntry::path)
3838
.collect::<Vec<_>>(),
3939
&resolver,
40-
pyproject_strategy,
40+
pyproject_config,
4141
);
4242

4343
let start = Instant::now();
@@ -50,7 +50,7 @@ pub fn add_noqa(
5050
.parent()
5151
.and_then(|parent| package_roots.get(parent))
5252
.and_then(|package| *package);
53-
let settings = resolver.resolve(path, pyproject_strategy);
53+
let settings = resolver.resolve(path, pyproject_config);
5454
match add_noqa_to_path(path, package, settings) {
5555
Ok(count) => Some(count),
5656
Err(e) => {

0 commit comments

Comments
 (0)