Skip to content

Commit

Permalink
Detect imports in src layouts by default (#12848)
Browse files Browse the repository at this point in the history
## Summary

Occasionally, we receive bug reports that imports in `src` directories
aren't correctly detected. The root of the problem is that we default to
`src = ["."]`, so users have to set `src = ["src"]` explicitly. This PR
extends the default to cover _both_ of them: `src = [".", "src"]`.

Closes #12454.

## Test Plan

I replicated the structure described in
#12453, and verified that the
imports were considered sorted, but that adding `src = ["."]` showed an
error.
  • Loading branch information
charliermarsh authored and AlexWaygood committed Aug 14, 2024
1 parent d2bfcf1 commit 58d5d97
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 32 deletions.
8 changes: 2 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ linter.logger_objects = []
linter.namespace_packages = []
linter.src = [
"[BASEPATH]",
"[BASEPATH]/src",
]
linter.tab_size = 4
linter.line_length = 88
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
6 changes: 3 additions & 3 deletions crates/ruff_workspace/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
30 changes: 17 additions & 13 deletions crates/ruff_workspace/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 8 additions & 7 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion docs/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down
2 changes: 1 addition & 1 deletion ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 58d5d97

Please sign in to comment.