diff --git a/resources/test/package/pyproject.toml b/resources/test/package/pyproject.toml index 4335ce71c51b9..84b5cc9d5485e 100644 --- a/resources/test/package/pyproject.toml +++ b/resources/test/package/pyproject.toml @@ -1,2 +1,7 @@ [tool.ruff] src = ["."] + +# This will make sure that `exclude` paths are rooted +# to where the configuration file was found; this file exists +# in a `resources/test` hierarchy. +exclude = ["resources"] diff --git a/resources/test/package/resources/ignored.py b/resources/test/package/resources/ignored.py new file mode 100644 index 0000000000000..cf46a53ec86de --- /dev/null +++ b/resources/test/package/resources/ignored.py @@ -0,0 +1,4 @@ +# This file should be ignored, but it would otherwise trigger +# an unused import error: + +import math diff --git a/src/resolver.rs b/src/resolver.rs index d7d5c1c9fa8af..f40e91080ad6b 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -411,6 +411,11 @@ fn is_file_excluded( return true; } } + if path == settings.project_root { + // Bail out; we'd end up past the project root on the next iteration + // (excludes etc. are thus "rooted" to the project). + break; + } } false } @@ -424,8 +429,13 @@ mod tests { use path_absolutize::Absolutize; use crate::fs; - use crate::resolver::{is_python_path, match_exclusion}; + use crate::resolver::{ + is_file_excluded, is_python_path, match_exclusion, resolve_settings_with_processor, + NoOpProcessor, PyprojectDiscovery, Relativity, Resolver, + }; + use crate::settings::pyproject::find_settings_toml; use crate::settings::types::FilePattern; + use crate::test::test_resource_path; #[test] fn inclusions() { @@ -567,4 +577,30 @@ mod tests { Ok(()) } + + #[test] + 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, + )?); + // 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, + )); + // 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, + )); + Ok(()) + } } diff --git a/src/settings/defaults.rs b/src/settings/defaults.rs index 2f696073b014c..46dd123a8acb8 100644 --- a/src/settings/defaults.rs +++ b/src/settings/defaults.rs @@ -71,6 +71,7 @@ impl Default for Settings { respect_gitignore: true, show_source: false, src: vec![path_dedot::CWD.clone()], + project_root: path_dedot::CWD.clone(), target_version: TARGET_VERSION, task_tags: TASK_TAGS.iter().map(ToString::to_string).collect(), typing_modules: vec![], diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 185a1d3a7e83d..ff900ec574e46 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -90,6 +90,7 @@ pub struct Settings { pub extend_exclude: HashableGlobSet, pub force_exclude: bool, pub respect_gitignore: bool, + pub project_root: PathBuf, // Rule-specific settings pub allowed_confusables: HashableHashSet, @@ -167,6 +168,7 @@ impl Settings { src: config .src .unwrap_or_else(|| vec![project_root.to_path_buf()]), + project_root: project_root.to_path_buf(), target_version: config.target_version.unwrap_or(defaults::TARGET_VERSION), task_tags: config.task_tags.unwrap_or_else(|| { defaults::TASK_TAGS