Skip to content

Commit 15aa5a6

Browse files
charliermarshMichaReiser
authored andcommitted
Detect imports in src layouts by default (#12848)
## 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.
1 parent 33512a4 commit 15aa5a6

File tree

8 files changed

+34
-32
lines changed

8 files changed

+34
-32
lines changed

CONTRIBUTING.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -911,9 +911,5 @@ There are three ways in which an import can be categorized as "first-party":
911911
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a
912912
file `foo.py`.
913913
914-
By default, `src` is set to the project root. In the above example, we'd want to set
915-
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
916-
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
917-
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
918-
the same-package heuristic; but if your project contains multiple packages, you'll want to set `src`
919-
explicitly.
914+
By default, `src` is set to the project root, along with `"src"` subdirectory in the project root.
915+
This ensures that Ruff supports both flat and "src" layouts out of the box.

crates/ruff/tests/snapshots/show_settings__display_default_settings.snap

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ linter.logger_objects = []
209209
linter.namespace_packages = []
210210
linter.src = [
211211
"[BASEPATH]",
212+
"[BASEPATH]/src",
212213
]
213214
linter.tab_size = 4
214215
linter.line_length = 88

crates/ruff_linter/src/settings/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ impl LinterSettings {
398398
per_file_ignores: CompiledPerFileIgnoreList::default(),
399399
fix_safety: FixSafetyTable::default(),
400400

401-
src: vec![path_dedot::CWD.clone()],
401+
src: vec![path_dedot::CWD.clone(), path_dedot::CWD.join("src")],
402402
// Needs duplicating
403403
tab_size: IndentWidth::default(),
404404
line_length: LineLength::default(),

crates/ruff_workspace/src/configuration.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,6 @@ impl Configuration {
271271
.chain(lint.extend_per_file_ignores)
272272
.collect(),
273273
)?,
274-
275274
fix_safety: FixSafetyTable::from_rule_selectors(
276275
&lint.extend_safe_fixes,
277276
&lint.extend_unsafe_fixes,
@@ -280,8 +279,9 @@ impl Configuration {
280279
require_explicit: false,
281280
},
282281
),
283-
284-
src: self.src.unwrap_or_else(|| vec![project_root.to_path_buf()]),
282+
src: self
283+
.src
284+
.unwrap_or_else(|| vec![project_root.to_path_buf(), project_root.join("src")]),
285285
explicit_preview_rules: lint.explicit_preview_rules.unwrap_or_default(),
286286

287287
task_tags: lint

crates/ruff_workspace/src/options.rs

+17-13
Original file line numberDiff line numberDiff line change
@@ -323,33 +323,37 @@ pub struct Options {
323323
/// The directories to consider when resolving first- vs. third-party
324324
/// imports.
325325
///
326-
/// As an example: given a Python package structure like:
326+
/// When omitted, the `src` directory will typically default to including both:
327+
///
328+
/// 1. The directory containing the nearest `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (the "project root").
329+
/// 2. The `"src"` subdirectory of the project root.
330+
///
331+
/// These defaults ensure that uv supports both flat layouts and `src` layouts out-of-the-box.
332+
/// (If a configuration file is explicitly provided (e.g., via the `--config` command-line
333+
/// flag), the current working directory will be considered the project root.)
334+
///
335+
/// As an example, consider an alternative project structure, like:
327336
///
328337
/// ```text
329338
/// my_project
330339
/// ├── pyproject.toml
331-
/// └── src
340+
/// └── lib
332341
/// └── my_package
333342
/// ├── __init__.py
334343
/// ├── foo.py
335344
/// └── bar.py
336345
/// ```
337346
///
338-
/// The `./src` directory should be included in the `src` option
339-
/// (e.g., `src = ["src"]`), such that when resolving imports,
340-
/// `my_package.foo` is considered a first-party import.
341-
///
342-
/// When omitted, the `src` directory will typically default to the
343-
/// directory containing the nearest `pyproject.toml`, `ruff.toml`, or
344-
/// `.ruff.toml` file (the "project root"), unless a configuration file
345-
/// is explicitly provided (e.g., via the `--config` command-line flag).
347+
/// In this case, the `./lib` directory should be included in the `src` option
348+
/// (e.g., `src = ["lib"]`), such that when resolving imports, `my_package.foo`
349+
/// is considered first-party.
346350
///
347351
/// This field supports globs. For example, if you have a series of Python
348352
/// packages in a `python_modules` directory, `src = ["python_modules/*"]`
349-
/// would expand to incorporate all of the packages in that directory. User
350-
/// home directory and environment variables will also be expanded.
353+
/// would expand to incorporate all packages in that directory. User home
354+
/// directory and environment variables will also be expanded.
351355
#[option(
352-
default = r#"["."]"#,
356+
default = r#"[".", "src"]"#,
353357
value_type = "list[str]",
354358
example = r#"
355359
# Allow imports relative to the "src" and "test" directories.

docs/faq.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -292,13 +292,14 @@ When Ruff sees an import like `import foo`, it will then iterate over the `src`
292292
looking for a corresponding Python module (in reality, a directory named `foo` or a file named
293293
`foo.py`).
294294

295-
If the `src` field is omitted, Ruff will default to using the "project root" as the only
296-
first-party source. The "project root" is typically the directory containing your `pyproject.toml`,
297-
`ruff.toml`, or `.ruff.toml` file, unless a configuration file is provided on the command-line via
298-
the `--config` option, in which case, the current working directory is used as the project root.
299-
300-
In this case, Ruff would only check the top-level directory. Instead, we can configure Ruff to
301-
consider `src` as a first-party source like so:
295+
If the `src` field is omitted, Ruff will default to using the "project root", along with a `"src"`
296+
subdirectory, as the first-party sources, to support both flat and nested project layouts.
297+
The "project root" is typically the directory containing your `pyproject.toml`, `ruff.toml`, or
298+
`.ruff.toml` file, unless a configuration file is provided on the command-line via the `--config`
299+
option, in which case, the current working directory is used as the project root.
300+
301+
In this case, Ruff would check the `"src"` directory by default, but we can configure it as an
302+
explicit, exclusive first-party source like so:
302303

303304
=== "pyproject.toml"
304305

docs/integrations.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Alternatively, you can include `ruff-action` as a step in any other workflow fil
5959

6060
- `version`: The Ruff version to install (default: latest).
6161
- `args`: The command-line arguments to pass to Ruff (default: `"check"`).
62-
- `src`: The source paths to pass to Ruff (default: `"."`).
62+
- `src`: The source paths to pass to Ruff (default: `[".", "src"]`).
6363

6464
For example, to run `ruff check --select B ./src` using Ruff version `0.0.259`:
6565

ruff.schema.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)