Skip to content

Commit 75e0142

Browse files
Always place non-relative imports after relative imports (#10669)
## Summary When `relative-imports-order = "closest-to-furthest"` is set, we should _still_ put non-relative imports after relative imports. It's rare for them to be in the same section, but _possible_ if you use `known-local-folder`. Closes #10655. ## Test Plan New tests. Also sorted this file: ```python from ..models import ABC from .models import Question from .utils import create_question from django_polls.apps.polls.models import Choice ``` With both: - `isort view.py` - `ruff check view.py --select I --fix` And the following `pyproject.toml`: ```toml [tool.ruff.lint.isort] order-by-type = false relative-imports-order = "closest-to-furthest" known-local-folder = ["django_polls"] [tool.isort] profile = "black" reverse_relative = true known_local_folder = ["django_polls"] ``` I verified that Ruff and isort gave the same result, and that they _still_ gave the same result when removing the relevant setting: ```toml [tool.ruff.lint.isort] order-by-type = false known-local-folder = ["django_polls"] [tool.isort] profile = "black" known_local_folder = ["django_polls"] ```
1 parent fc54f53 commit 75e0142

6 files changed

+86
-21
lines changed

crates/ruff_linter/resources/test/fixtures/isort/separate_local_folder_imports.py

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
import leading_prefix
44
import os
55
from . import leading_prefix
6+
from .. import trailing_prefix
7+
from ruff import check

crates/ruff_linter/src/rules/isort/mod.rs

+25
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,31 @@ mod tests {
471471
Ok(())
472472
}
473473

474+
#[test_case(Path::new("separate_local_folder_imports.py"))]
475+
fn known_local_folder_closest(path: &Path) -> Result<()> {
476+
let snapshot = format!("known_local_folder_closest_{}", path.to_string_lossy());
477+
let diagnostics = test_path(
478+
Path::new("isort").join(path).as_path(),
479+
&LinterSettings {
480+
isort: super::settings::Settings {
481+
known_modules: KnownModules::new(
482+
vec![],
483+
vec![],
484+
vec![pattern("ruff")],
485+
vec![],
486+
FxHashMap::default(),
487+
),
488+
relative_imports_order: RelativeImportsOrder::ClosestToFurthest,
489+
..super::settings::Settings::default()
490+
},
491+
src: vec![test_resource_path("fixtures/isort")],
492+
..LinterSettings::for_rule(Rule::UnsortedImports)
493+
},
494+
)?;
495+
assert_messages!(snapshot, diagnostics);
496+
Ok(())
497+
}
498+
474499
#[test_case(Path::new("case_sensitive.py"))]
475500
fn case_sensitive(path: &Path) -> Result<()> {
476501
let snapshot = format!("case_sensitive_{}", path.to_string_lossy());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
source: crates/ruff_linter/src/rules/isort/mod.rs
3+
---
4+
separate_local_folder_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted
5+
|
6+
1 | / import sys
7+
2 | | import ruff
8+
3 | | import leading_prefix
9+
4 | | import os
10+
5 | | from . import leading_prefix
11+
6 | | from .. import trailing_prefix
12+
7 | | from ruff import check
13+
|
14+
= help: Organize imports
15+
16+
Safe fix
17+
1 |+import os
18+
1 2 | import sys
19+
3 |+
20+
4 |+import leading_prefix
21+
5 |+
22+
2 6 | import ruff
23+
3 |-import leading_prefix
24+
4 |-import os
25+
5 7 | from . import leading_prefix
26+
6 8 | from .. import trailing_prefix
27+
7 9 | from ruff import check

crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__known_local_folder_separate_local_folder_imports.py.snap

+6-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ separate_local_folder_imports.py:1:1: I001 [*] Import block is un-sorted or un-f
88
3 | | import leading_prefix
99
4 | | import os
1010
5 | | from . import leading_prefix
11+
6 | | from .. import trailing_prefix
12+
7 | | from ruff import check
1113
|
1214
= help: Organize imports
1315

@@ -20,6 +22,7 @@ separate_local_folder_imports.py:1:1: I001 [*] Import block is un-sorted or un-f
2022
2 6 | import ruff
2123
3 |-import leading_prefix
2224
4 |-import os
23-
5 7 | from . import leading_prefix
24-
25-
25+
7 |+from .. import trailing_prefix
26+
5 8 | from . import leading_prefix
27+
6 |-from .. import trailing_prefix
28+
7 9 | from ruff import check

crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__separate_local_folder_imports.py.snap

+15-11
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ separate_local_folder_imports.py:1:1: I001 [*] Import block is un-sorted or un-f
88
3 | | import leading_prefix
99
4 | | import os
1010
5 | | from . import leading_prefix
11+
6 | | from .. import trailing_prefix
12+
7 | | from ruff import check
1113
|
1214
= help: Organize imports
1315

1416
Safe fix
15-
1 |+import os
16-
1 2 | import sys
17-
3 |+
18-
2 4 | import ruff
19-
5 |+
20-
3 6 | import leading_prefix
21-
4 |-import os
22-
7 |+
23-
5 8 | from . import leading_prefix
24-
25-
17+
1 |+import os
18+
1 2 | import sys
19+
3 |+
20+
2 4 | import ruff
21+
5 |+from ruff import check
22+
6 |+
23+
3 7 | import leading_prefix
24+
4 |-import os
25+
8 |+
26+
9 |+from .. import trailing_prefix
27+
5 10 | from . import leading_prefix
28+
6 |-from .. import trailing_prefix
29+
7 |-from ruff import check

crates/ruff_linter/src/rules/isort/sorting.rs

+11-7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ impl<'a> From<String> for NatOrdStr<'a> {
6969
pub(crate) enum Distance {
7070
Nearest(u32),
7171
Furthest(Reverse<u32>),
72+
None,
7273
}
7374

7475
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
@@ -101,17 +102,20 @@ impl<'a> ModuleKey<'a> {
101102
style: ImportStyle,
102103
settings: &Settings,
103104
) -> Self {
104-
let level = level.unwrap_or_default();
105-
106105
let force_to_top = !name.is_some_and(|name| settings.force_to_top.contains(name)); // `false` < `true` so we get forced to top first
107106

108107
let maybe_length = (settings.length_sort
109108
|| (settings.length_sort_straight && style == ImportStyle::Straight))
110-
.then_some(name.map(str::width).unwrap_or_default() + level as usize);
111-
112-
let distance = match settings.relative_imports_order {
113-
RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level),
114-
RelativeImportsOrder::FurthestToClosest => Distance::Furthest(Reverse(level)),
109+
.then_some(
110+
name.map(str::width).unwrap_or_default() + level.unwrap_or_default() as usize,
111+
);
112+
113+
let distance = match level {
114+
None | Some(0) => Distance::None,
115+
Some(level) => match settings.relative_imports_order {
116+
RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level),
117+
RelativeImportsOrder::FurthestToClosest => Distance::Furthest(Reverse(level)),
118+
},
115119
};
116120

117121
let maybe_lowercase_name = name.and_then(|name| {

0 commit comments

Comments
 (0)