From c936eb8483c9d44a094d1a619a30f9d5de21d5c2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 12 Aug 2024 13:35:26 -0400 Subject: [PATCH] Detect imports in src layouts by default --- CONTRIBUTING.md | 8 ++--- ...ow_settings__display_default_settings.snap | 1 + crates/ruff_linter/src/settings/mod.rs | 2 +- crates/ruff_workspace/src/configuration.rs | 6 ++-- crates/ruff_workspace/src/options.rs | 30 +++++++++++-------- docs/faq.md | 15 +++++----- docs/integrations.md | 2 +- ruff.schema.json | 2 +- 8 files changed, 34 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8096a0d1a4cf8..f1962aedbe06e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -911,9 +911,5 @@ There are three ways in which an import can be categorized as "first-party": the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a file `foo.py`. -By default, `src` is set to the project root. In the above example, we'd want to set -`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo` -as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is -unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via -the same-package heuristic; but if your project contains multiple packages, you'll want to set `src` -explicitly. +By default, `src` is set to the project root, along with `"src"` subdirectory in the project root. +This ensures that Ruff supports both flat and "src" layouts out of the box. diff --git a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap index bbd2e822c4767..f181e3e560afe 100644 --- a/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap +++ b/crates/ruff/tests/snapshots/show_settings__display_default_settings.snap @@ -209,6 +209,7 @@ linter.logger_objects = [] linter.namespace_packages = [] linter.src = [ "[BASEPATH]", + "[BASEPATH]/src", ] linter.tab_size = 4 linter.line_length = 88 diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index 3099e47d33cf9..a0c319bf46641 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -398,7 +398,7 @@ impl LinterSettings { per_file_ignores: CompiledPerFileIgnoreList::default(), fix_safety: FixSafetyTable::default(), - src: vec![path_dedot::CWD.clone()], + src: vec![path_dedot::CWD.clone(), path_dedot::CWD.join("src")], // Needs duplicating tab_size: IndentWidth::default(), line_length: LineLength::default(), diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index 74c7a36806920..f745c0b360d0d 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -271,7 +271,6 @@ impl Configuration { .chain(lint.extend_per_file_ignores) .collect(), )?, - fix_safety: FixSafetyTable::from_rule_selectors( &lint.extend_safe_fixes, &lint.extend_unsafe_fixes, @@ -280,8 +279,9 @@ impl Configuration { require_explicit: false, }, ), - - src: self.src.unwrap_or_else(|| vec![project_root.to_path_buf()]), + src: self + .src + .unwrap_or_else(|| vec![project_root.to_path_buf(), project_root.join("src")]), explicit_preview_rules: lint.explicit_preview_rules.unwrap_or_default(), task_tags: lint diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index cda010de67d35..af36dcea9016a 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -323,33 +323,37 @@ pub struct Options { /// The directories to consider when resolving first- vs. third-party /// imports. /// - /// As an example: given a Python package structure like: + /// When omitted, the `src` directory will typically default to including both: + /// + /// 1. The directory containing the nearest `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (the "project root"). + /// 2. The `"src"` subdirectory of the project root. + /// + /// These defaults ensure that uv supports both flat layouts and `src` layouts out-of-the-box. + /// (If a configuration file is explicitly provided (e.g., via the `--config` command-line + /// flag), the current working directory will be considered the project root.) + /// + /// As an example, consider an alternative project structure, like: /// /// ```text /// my_project /// ├── pyproject.toml - /// └── src + /// └── lib /// └── my_package /// ├── __init__.py /// ├── foo.py /// └── bar.py /// ``` /// - /// The `./src` directory should be included in the `src` option - /// (e.g., `src = ["src"]`), such that when resolving imports, - /// `my_package.foo` is considered a first-party import. - /// - /// When omitted, the `src` directory will typically default to the - /// directory containing the nearest `pyproject.toml`, `ruff.toml`, or - /// `.ruff.toml` file (the "project root"), unless a configuration file - /// is explicitly provided (e.g., via the `--config` command-line flag). + /// In this case, the `./lib` directory should be included in the `src` option + /// (e.g., `src = ["lib"]`), such that when resolving imports, `my_package.foo` + /// is considered first-party. /// /// This field supports globs. For example, if you have a series of Python /// packages in a `python_modules` directory, `src = ["python_modules/*"]` - /// would expand to incorporate all of the packages in that directory. User - /// home directory and environment variables will also be expanded. + /// would expand to incorporate all packages in that directory. User home + /// directory and environment variables will also be expanded. #[option( - default = r#"["."]"#, + default = r#"[".", "src"]"#, value_type = "list[str]", example = r#" # Allow imports relative to the "src" and "test" directories. diff --git a/docs/faq.md b/docs/faq.md index 6a4153601ea80..20ee678692b0f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -292,13 +292,14 @@ When Ruff sees an import like `import foo`, it will then iterate over the `src` looking for a corresponding Python module (in reality, a directory named `foo` or a file named `foo.py`). -If the `src` field is omitted, Ruff will default to using the "project root" as the only -first-party source. The "project root" is typically the directory containing your `pyproject.toml`, -`ruff.toml`, or `.ruff.toml` file, unless a configuration file is provided on the command-line via -the `--config` option, in which case, the current working directory is used as the project root. - -In this case, Ruff would only check the top-level directory. Instead, we can configure Ruff to -consider `src` as a first-party source like so: +If the `src` field is omitted, Ruff will default to using the "project root", along with a `"src"` +subdirectory, as the first-party sources, to support both flat and nested project layouts. +The "project root" is typically the directory containing your `pyproject.toml`, `ruff.toml`, or +`.ruff.toml` file, unless a configuration file is provided on the command-line via the `--config` +option, in which case, the current working directory is used as the project root. + +In this case, Ruff would check the `"src"` directory by default, but we can configure it as an +explicit, exclusive first-party source like so: === "pyproject.toml" diff --git a/docs/integrations.md b/docs/integrations.md index ca9fb8cefa599..c8ef3fd82e3d3 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -59,7 +59,7 @@ Alternatively, you can include `ruff-action` as a step in any other workflow fil - `version`: The Ruff version to install (default: latest). - `args`: The command-line arguments to pass to Ruff (default: `"check"`). -- `src`: The source paths to pass to Ruff (default: `"."`). +- `src`: The source paths to pass to Ruff (default: `[".", "src"]`). For example, to run `ruff check --select B ./src` using Ruff version `0.0.259`: diff --git a/ruff.schema.json b/ruff.schema.json index c81d089d7ed53..56cc1b45b43c0 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -671,7 +671,7 @@ ] }, "src": { - "description": "The directories to consider when resolving first- vs. third-party imports.\n\nAs an example: given a Python package structure like:\n\n```text my_project ├── pyproject.toml └── src └── my_package ├── __init__.py ├── foo.py └── bar.py ```\n\nThe `./src` directory should be included in the `src` option (e.g., `src = [\"src\"]`), such that when resolving imports, `my_package.foo` is considered a first-party import.\n\nWhen omitted, the `src` directory will typically default to the directory containing the nearest `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (the \"project root\"), unless a configuration file is explicitly provided (e.g., via the `--config` command-line flag).\n\nThis field supports globs. For example, if you have a series of Python packages in a `python_modules` directory, `src = [\"python_modules/*\"]` would expand to incorporate all of the packages in that directory. User home directory and environment variables will also be expanded.", + "description": "The directories to consider when resolving first- vs. third-party imports.\n\nWhen omitted, the `src` directory will typically default to including both:\n\n1. The directory containing the nearest `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (the \"project root\"). 2. The `\"src\"` subdirectory of the project root.\n\nThese defaults ensure that uv supports both flat layouts and `src` layouts out-of-the-box. (If a configuration file is explicitly provided (e.g., via the `--config` command-line flag), the current working directory will be considered the project root.)\n\nAs an example, consider an alternative project structure, like:\n\n```text my_project ├── pyproject.toml └── lib └── my_package ├── __init__.py ├── foo.py └── bar.py ```\n\nIn this case, the `./lib` directory should be included in the `src` option (e.g., `src = [\"lib\"]`), such that when resolving imports, `my_package.foo` is considered first-party.\n\nThis field supports globs. For example, if you have a series of Python packages in a `python_modules` directory, `src = [\"python_modules/*\"]` would expand to incorporate all packages in that directory. User home directory and environment variables will also be expanded.", "type": [ "array", "null"