diff --git a/README.md b/README.md index 14e27e9055..4f408c2938 100644 --- a/README.md +++ b/README.md @@ -343,15 +343,18 @@ directory hierarchy is used for every individual file, with all paths in the `py There are a few exceptions to these rules: -1. If a configuration file is passed directly via `--config`, those settings are used for across +1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignore any + `pyproject.toml` files that lack a `[tool.ruff]` section. +2. If a configuration file is passed directly via `--config`, those settings are used for across files. Any relative paths in that configuration file (like `exclude` globs or `src` paths) are resolved relative to the _current working directory_. -2. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using - a default configuration. If a user-specific configuration file exists at `${config_dir}/ruff/pyproject.toml`, - that file will be used instead of the default configuration, with `${config_dir}` being determined - via the [`dirs](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all relative paths - being again resolved relative to the _current working directory_. -3. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via +3. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using + a default configuration. If a user-specific configuration file exists + at `${config_dir}/ruff/pyproject.toml`, + that file will be used instead of the default configuration, with `${config_dir}` being + determined via the [`dirs](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate, and all + relative paths being again resolved relative to the _current working directory_. +4. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via `--select`) will override the settings in _every_ resolved configuration file. Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy), diff --git a/resources/test/project/README.md b/resources/test/project/README.md index 32da57ea8a..1dccd78269 100644 --- a/resources/test/project/README.md +++ b/resources/test/project/README.md @@ -9,30 +9,32 @@ Running from the repo root should pick up and enforce the appropriate settings f ``` ∴ cargo run resources/test/project/ -Found 7 error(s). -resources/test/project/examples/.dotfiles/script.py:1:8: F401 `os` imported but unused -resources/test/project/examples/.dotfiles/script.py:5:5: F841 Local variable `x` is assigned to but never used +Found 8 error(s). +resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted +resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused +resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used resources/test/project/src/file.py:1:8: F401 `os` imported but unused resources/test/project/src/file.py:5:5: F841 Local variable `x` is assigned to but never used resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted -4 potentially fixable with the --fix option. +6 potentially fixable with the --fix option. ``` Running from the project directory itself should exhibit the same behavior: ``` ∴ (cd resources/test/project/ && cargo run .) -Found 7 error(s). -examples/.dotfiles/script.py:1:8: F401 `os` imported but unused -examples/.dotfiles/script.py:5:5: F841 Local variable `x` is assigned to but never used +Found 8 error(s). +examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted +examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused +examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used src/file.py:1:8: F401 `os` imported but unused src/file.py:5:5: F841 Local variable `x` is assigned to but never used src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted -4 potentially fixable with the --fix option. +6 potentially fixable with the --fix option. ``` Running from the sub-package directory should exhibit the same behavior, but omit the top-level @@ -51,19 +53,22 @@ file paths from the current working directory: ``` ∴ (cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/) -Found 11 error(s). -resources/test/project/examples/.dotfiles/script.py:1:8: F401 `os` imported but unused -resources/test/project/examples/.dotfiles/script.py:5:5: F841 Local variable `x` is assigned to but never used +Found 14 error(s). +resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted +resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused +resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused resources/test/project/examples/docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but unused resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used +resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused +resources/test/project/examples/excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used resources/test/project/src/file.py:1:8: F401 `os` imported but unused resources/test/project/src/file.py:5:5: F841 Local variable `x` is assigned to but never used resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted -7 potentially fixable with the --fix option. +10 potentially fixable with the --fix option. ``` Running from a parent directory should this "ignore" the `exclude` (hence, `concepts/file.py` gets @@ -72,10 +77,10 @@ included in the output): ``` ∴ (cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .) Found 4 error(s). -.dotfiles/script.py:5:5: F841 Local variable `x` is assigned to but never used docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used +excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used 1 potentially fixable with the --fix option. ``` diff --git a/resources/test/project/examples/.dotfiles/pyproject.toml b/resources/test/project/examples/.dotfiles/pyproject.toml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/resources/test/project/examples/.dotfiles/script.py b/resources/test/project/examples/.dotfiles/script.py index 4a49d9883f..9c729c9e65 100755 --- a/resources/test/project/examples/.dotfiles/script.py +++ b/resources/test/project/examples/.dotfiles/script.py @@ -1,5 +1,2 @@ -import os - - -def f(): - x = 1 +import numpy as np +from app import app_file diff --git a/src/lib.rs b/src/lib.rs index ccdc0d396f..8177f1d446 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,7 @@ pub mod visibility; /// Load the relevant `Settings` for a given `Path`. fn resolve(path: &Path) -> Result { - if let Some(pyproject) = pyproject::find_pyproject_toml(path) { + if let Some(pyproject) = pyproject::find_pyproject_toml(path)? { // First priority: `pyproject.toml` in the current `Path`. resolver::resolve_settings(&pyproject, &Relativity::Parent, None) } else if let Some(pyproject) = pyproject::find_user_pyproject_toml() { diff --git a/src/main.rs b/src/main.rs index 51e45fa83d..fc58eadaad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ fn resolve(config: Option, overrides: &Overrides) -> Result resolver.write().unwrap().add(root, settings), + match has_ruff_section(&pyproject) { + Ok(false) => {} + Ok(true) => { + match resolve_scoped_settings( + &pyproject, + &Relativity::Parent, + Some(overrides), + ) { + Ok((root, settings)) => { + resolver.write().unwrap().add(root, settings); + } + Err(err) => { + *error.lock().unwrap() = Err(err); + return WalkState::Quit; + } + } + } Err(err) => { *error.lock().unwrap() = Err(err); return WalkState::Quit; @@ -272,8 +286,8 @@ pub fn python_files_in_path( return WalkState::Skip; } } - Err(e) => { - debug!("Ignored path due to error in parsing: {:?}: {}", path, e); + Err(err) => { + debug!("Ignored path due to error in parsing: {:?}: {}", path, err); return WalkState::Skip; } } diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index ed5c748685..a5fe0ab677 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -33,17 +33,24 @@ fn parse_pyproject_toml(path: &Path) -> Result { toml::from_str(&contents).map_err(std::convert::Into::into) } -/// Find the nearest `pyproject.toml` file. -pub fn find_pyproject_toml(path: &Path) -> Option { - for directory in path.ancestors() { - let pyproject = directory.join("pyproject.toml"); - if pyproject.is_file() { - return Some(pyproject); - } - } - None +/// Return `true` if a `pyproject.toml` contains a `[tool.ruff]` section. +pub fn has_ruff_section(path: &Path) -> Result { + let pyproject = parse_pyproject_toml(path)?; + Ok(pyproject.tool.and_then(|tool| tool.ruff).is_some()) } +/// Find the path to the `pyproject.toml` file, if such a file exists. +pub fn find_pyproject_toml(path: &Path) -> Result> { + for directory in path.ancestors() { + let pyproject = directory.join("pyproject.toml"); + if pyproject.is_file() && has_ruff_section(&pyproject)? { + return Ok(Some(pyproject)); + } + } + Ok(None) +} + +/// Find the path to the user-specific `pyproject.toml`, if it exists. pub fn find_user_pyproject_toml() -> Option { let mut path = dirs::config_dir()?; path.push("ruff"); @@ -55,6 +62,7 @@ pub fn find_user_pyproject_toml() -> Option { } } +/// Load `Options` from a `pyproject.toml`. pub fn load_options(pyproject: &Path) -> Result { Ok(parse_pyproject_toml(pyproject) .map_err(|err| anyhow!("Failed to parse `{}`: {}", pyproject.to_string_lossy(), err))? @@ -355,7 +363,7 @@ other-attribute = 1 fn find_and_parse_pyproject_toml() -> Result<()> { let cwd = current_dir()?; let pyproject = - find_pyproject_toml(&cwd.join("resources/test/fixtures/__init__.py")).unwrap(); + find_pyproject_toml(&cwd.join("resources/test/fixtures/__init__.py"))?.unwrap(); assert_eq!( pyproject, cwd.join("resources/test/fixtures/pyproject.toml")