From 578c60ef8ac878e41fb7e3dd67e03bf390095e4e Mon Sep 17 00:00:00 2001 From: bluthej Date: Mon, 30 Oct 2023 22:39:34 +0100 Subject: [PATCH 1/8] Add length-sort option, implement logic + tests --- .../isort/length_sort_from_imports.py | 3 +++ .../length_sort_straight_and_from_imports.py | 6 +++++ .../isort/length_sort_straight_imports.py | 4 +++ crates/ruff_linter/src/rules/isort/mod.rs | 20 ++++++++++++++ .../ruff_linter/src/rules/isort/settings.rs | 2 ++ ...t__tests__length_sort_from_imports.py.snap | 18 +++++++++++++ ...gth_sort_straight_and_from_imports.py.snap | 26 +++++++++++++++++++ ...ests__length_sort_straight_imports.py.snap | 21 +++++++++++++++ crates/ruff_linter/src/rules/isort/sorting.rs | 8 ++++++ crates/ruff_workspace/src/options.rs | 11 ++++++++ ruff.schema.json | 7 +++++ 11 files changed, 126 insertions(+) create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_from_imports.py create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_and_from_imports.py create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_imports.py create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_from_imports.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_from_imports.py new file mode 100644 index 0000000000000..bf6a5732d2236 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_from_imports.py @@ -0,0 +1,3 @@ +from mediuuuuuuuuuuum import a +from short import b +from loooooooooooooooooooooog import c diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_and_from_imports.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_and_from_imports.py new file mode 100644 index 0000000000000..738efb3549339 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_and_from_imports.py @@ -0,0 +1,6 @@ +import mediuuuuuum +import short +import looooooooooooooooong +from looooooooooooooong import a +from mediuuuum import c +from short import b diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_imports.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_imports.py new file mode 100644 index 0000000000000..70aabd043b021 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_straight_imports.py @@ -0,0 +1,4 @@ +import mediuuuuuumb +import short +import looooooooooooooooong +import mediuuuuuuma diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index 5fce5a86acfc8..c00d195bafe9d 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -1138,4 +1138,24 @@ mod tests { assert_messages!(diagnostics); Ok(()) } + + #[test_case(Path::new("length_sort_straight_imports.py"))] + #[test_case(Path::new("length_sort_from_imports.py"))] + #[test_case(Path::new("length_sort_straight_and_from_imports.py"))] + fn length_sort(path: &Path) -> Result<()> { + let snapshot = format!("{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("isort").join(path).as_path(), + &LinterSettings { + isort: super::settings::Settings { + length_sort: true, + ..super::settings::Settings::default() + }, + src: vec![test_resource_path("fixtures/isort")], + ..LinterSettings::for_rule(Rule::UnsortedImports) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/isort/settings.rs b/crates/ruff_linter/src/rules/isort/settings.rs index d4937520fa7aa..5755e220c1fb0 100644 --- a/crates/ruff_linter/src/rules/isort/settings.rs +++ b/crates/ruff_linter/src/rules/isort/settings.rs @@ -58,6 +58,7 @@ pub struct Settings { pub section_order: Vec, pub no_sections: bool, pub from_first: bool, + pub length_sort: bool, } impl Default for Settings { @@ -86,6 +87,7 @@ impl Default for Settings { section_order: ImportType::iter().map(ImportSection::Known).collect(), no_sections: false, from_first: false, + length_sort: false, } } } diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap new file mode 100644 index 0000000000000..8ed06ce6feb40 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / from mediuuuuuuuuuuum import a +2 | | from short import b +3 | | from loooooooooooooooooooooog import c + | + = help: Organize imports + +ℹ Fix + 1 |+from short import b +1 2 | from mediuuuuuuuuuuum import a +2 |-from short import b +3 3 | from loooooooooooooooooooooog import c + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap new file mode 100644 index 0000000000000..21596b0e6bd87 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_straight_and_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / import mediuuuuuum +2 | | import short +3 | | import looooooooooooooooong +4 | | from looooooooooooooong import a +5 | | from mediuuuum import c +6 | | from short import b + | + = help: Organize imports + +ℹ Fix + 1 |+import short +1 2 | import mediuuuuuum +2 |-import short +3 3 | import looooooooooooooooong +4 |-from looooooooooooooong import a + 4 |+from short import b +5 5 | from mediuuuum import c +6 |-from short import b + 6 |+from looooooooooooooong import a + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap new file mode 100644 index 0000000000000..ef1efbb81750e --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap @@ -0,0 +1,21 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_straight_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / import mediuuuuuumb +2 | | import short +3 | | import looooooooooooooooong +4 | | import mediuuuuuuma + | + = help: Organize imports + +ℹ Fix + 1 |+import short + 2 |+import mediuuuuuuma +1 3 | import mediuuuuuumb +2 |-import short +3 4 | import looooooooooooooooong +4 |-import mediuuuuuuma + + diff --git a/crates/ruff_linter/src/rules/isort/sorting.rs b/crates/ruff_linter/src/rules/isort/sorting.rs index cb6f01f730054..982dbb8f97ac6 100644 --- a/crates/ruff_linter/src/rules/isort/sorting.rs +++ b/crates/ruff_linter/src/rules/isort/sorting.rs @@ -76,6 +76,7 @@ pub(crate) enum Distance { pub(crate) struct ModuleKey<'a> { distance: Distance, force_to_top: Option, + maybe_length: Option, maybe_lowercase_name: Option>, module_name: Option>, first_alias: Option>, @@ -97,6 +98,9 @@ impl<'a> ModuleKey<'a> { } }; let force_to_top = name.map(|name| !settings.force_to_top.contains(name)); // `false` < `true` so we get forced to top first + let maybe_length = settings + .length_sort + .then_some(name.map(str::len).unwrap_or_default()); let maybe_lowercase_name = name.and_then(|name| { (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))) }); @@ -108,6 +112,7 @@ impl<'a> ModuleKey<'a> { Self { distance, force_to_top, + maybe_length, maybe_lowercase_name, module_name, first_alias, @@ -122,6 +127,7 @@ impl<'a> ModuleKey<'a> { pub(crate) struct MemberKey<'a> { not_star_import: bool, member_type: Option, + maybe_length: Option, maybe_lowercase_name: Option>, module_name: NatOrdStr<'a>, asname: Option>, @@ -133,6 +139,7 @@ impl<'a> MemberKey<'a> { let member_type = settings .order_by_type .then_some(member_type(name, settings)); + let maybe_length = settings.length_sort.then_some(name.len()); let maybe_lowercase_name = (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))); let module_name = NatOrdStr::from(name); @@ -141,6 +148,7 @@ impl<'a> MemberKey<'a> { Self { not_star_import, member_type, + maybe_length, maybe_lowercase_name, module_name, asname, diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 6d8569dca4bd1..e4ec61b738646 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2060,6 +2060,16 @@ pub struct IsortOptions { "# )] pub sections: Option>>, + + /// Sort imports by their string length. + #[option( + default = r#"false"#, + value_type = "bool", + example = r#" + length-sort = true + "# + )] + pub length_sort: Option, } impl IsortOptions { @@ -2234,6 +2244,7 @@ impl IsortOptions { section_order, no_sections, from_first, + length_sort: self.length_sort.unwrap_or(false), }) } } diff --git a/ruff.schema.json b/ruff.schema.json index 8ae7c3fe37fb2..f9c1af7a9b9e2 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1477,6 +1477,13 @@ "type": "string" } }, + "length-sort": { + "description": "Sort imports by their string length.", + "type": [ + "boolean", + "null" + ] + }, "lines-after-imports": { "description": "The number of blank lines to place after imports. Use `-1` for automatic determination.\n\nWhen using the formatter, only the values `-1`, `1`, and `2` are compatible because it enforces at least one empty and at most two empty lines after imports.", "type": [ From 32eff8c2efda0a12bff174d24747b61e8ed202f6 Mon Sep 17 00:00:00 2001 From: bluthej Date: Wed, 1 Nov 2023 23:07:10 +0100 Subject: [PATCH 2/8] Add length-sort-straight option + tests --- crates/ruff_linter/src/rules/isort/mod.rs | 22 +++++++++++++++++- crates/ruff_linter/src/rules/isort/order.rs | 15 ++++++++---- .../ruff_linter/src/rules/isort/settings.rs | 2 ++ ...th_sort__length_sort_from_imports.py.snap} | 0 ...th_sort_straight_and_from_imports.py.snap} | 0 ...ort__length_sort_straight_imports.py.snap} | 0 ...straight__length_sort_from_imports.py.snap | 18 +++++++++++++++ ...gth_sort_straight_and_from_imports.py.snap | 23 +++++++++++++++++++ ...ight__length_sort_straight_imports.py.snap | 21 +++++++++++++++++ crates/ruff_linter/src/rules/isort/sorting.rs | 11 +++++++-- crates/ruff_workspace/src/options.rs | 11 +++++++++ ruff.schema.json | 7 ++++++ 12 files changed, 123 insertions(+), 7 deletions(-) rename crates/ruff_linter/src/rules/isort/snapshots/{ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap => ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap} (100%) rename crates/ruff_linter/src/rules/isort/snapshots/{ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap => ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap} (100%) rename crates/ruff_linter/src/rules/isort/snapshots/{ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap => ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap} (100%) create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index c00d195bafe9d..bee212011e921 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -1143,7 +1143,7 @@ mod tests { #[test_case(Path::new("length_sort_from_imports.py"))] #[test_case(Path::new("length_sort_straight_and_from_imports.py"))] fn length_sort(path: &Path) -> Result<()> { - let snapshot = format!("{}", path.to_string_lossy()); + let snapshot = format!("length_sort__{}", path.to_string_lossy()); let diagnostics = test_path( Path::new("isort").join(path).as_path(), &LinterSettings { @@ -1158,4 +1158,24 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } + + #[test_case(Path::new("length_sort_straight_imports.py"))] + #[test_case(Path::new("length_sort_from_imports.py"))] + #[test_case(Path::new("length_sort_straight_and_from_imports.py"))] + fn length_sort_straight(path: &Path) -> Result<()> { + let snapshot = format!("length_sort_straight__{}", path.to_string_lossy()); + let diagnostics = test_path( + Path::new("isort").join(path).as_path(), + &LinterSettings { + isort: super::settings::Settings { + length_sort_straight: true, + ..super::settings::Settings::default() + }, + src: vec![test_resource_path("fixtures/isort")], + ..LinterSettings::for_rule(Rule::UnsortedImports) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/isort/order.rs b/crates/ruff_linter/src/rules/isort/order.rs index 1d7102b3cae35..01f8362a7ffec 100644 --- a/crates/ruff_linter/src/rules/isort/order.rs +++ b/crates/ruff_linter/src/rules/isort/order.rs @@ -56,21 +56,27 @@ pub(crate) fn order_imports<'a>( .map(Import) .chain(from_imports.map(ImportFrom)) .sorted_by_cached_key(|import| match import { - Import((alias, _)) => { - ModuleKey::from_module(Some(alias.name), alias.asname, None, None, settings) - } + Import((alias, _)) => ModuleKey::from_module( + Some(alias.name), + alias.asname, + None, + None, + settings, + true, + ), ImportFrom((import_from, _, _, aliases)) => ModuleKey::from_module( import_from.module, None, import_from.level, aliases.first().map(|(alias, _)| (alias.name, alias.asname)), settings, + false, ), }) .collect() } else { let ordered_straight_imports = straight_imports.sorted_by_cached_key(|(alias, _)| { - ModuleKey::from_module(Some(alias.name), alias.asname, None, None, settings) + ModuleKey::from_module(Some(alias.name), alias.asname, None, None, settings, true) }); let ordered_from_imports = from_imports.sorted_by_cached_key(|(import_from, _, _, aliases)| { @@ -80,6 +86,7 @@ pub(crate) fn order_imports<'a>( import_from.level, aliases.first().map(|(alias, _)| (alias.name, alias.asname)), settings, + false, ) }); if settings.from_first { diff --git a/crates/ruff_linter/src/rules/isort/settings.rs b/crates/ruff_linter/src/rules/isort/settings.rs index 5755e220c1fb0..2a28a3c8b396b 100644 --- a/crates/ruff_linter/src/rules/isort/settings.rs +++ b/crates/ruff_linter/src/rules/isort/settings.rs @@ -59,6 +59,7 @@ pub struct Settings { pub no_sections: bool, pub from_first: bool, pub length_sort: bool, + pub length_sort_straight: bool, } impl Default for Settings { @@ -88,6 +89,7 @@ impl Default for Settings { no_sections: false, from_first: false, length_sort: false, + length_sort_straight: false, } } } diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_from_imports.py.snap rename to crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_and_from_imports.py.snap rename to crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap similarity index 100% rename from crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight_imports.py.snap rename to crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap new file mode 100644 index 0000000000000..fdd3e96fbd816 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap @@ -0,0 +1,18 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / from mediuuuuuuuuuuum import a +2 | | from short import b +3 | | from loooooooooooooooooooooog import c + | + = help: Organize imports + +ℹ Fix + 1 |+from loooooooooooooooooooooog import c +1 2 | from mediuuuuuuuuuuum import a +2 3 | from short import b +3 |-from loooooooooooooooooooooog import c + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap new file mode 100644 index 0000000000000..2329b0d47343c --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_straight_and_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / import mediuuuuuum +2 | | import short +3 | | import looooooooooooooooong +4 | | from looooooooooooooong import a +5 | | from mediuuuum import c +6 | | from short import b + | + = help: Organize imports + +ℹ Fix + 1 |+import short +1 2 | import mediuuuuuum +2 |-import short +3 3 | import looooooooooooooooong +4 4 | from looooooooooooooong import a +5 5 | from mediuuuum import c + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap new file mode 100644 index 0000000000000..ef1efbb81750e --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap @@ -0,0 +1,21 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_straight_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / import mediuuuuuumb +2 | | import short +3 | | import looooooooooooooooong +4 | | import mediuuuuuuma + | + = help: Organize imports + +ℹ Fix + 1 |+import short + 2 |+import mediuuuuuuma +1 3 | import mediuuuuuumb +2 |-import short +3 4 | import looooooooooooooooong +4 |-import mediuuuuuuma + + diff --git a/crates/ruff_linter/src/rules/isort/sorting.rs b/crates/ruff_linter/src/rules/isort/sorting.rs index 982dbb8f97ac6..b85e0d37c7421 100644 --- a/crates/ruff_linter/src/rules/isort/sorting.rs +++ b/crates/ruff_linter/src/rules/isort/sorting.rs @@ -90,6 +90,7 @@ impl<'a> ModuleKey<'a> { level: Option, first_alias: Option<(&'a str, Option<&'a str>)>, settings: &Settings, + straight_import: bool, ) -> Self { let distance = match settings.relative_imports_order { RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level.unwrap_or_default()), @@ -97,15 +98,21 @@ impl<'a> ModuleKey<'a> { Distance::Furthest(Reverse(level.unwrap_or_default())) } }; + let force_to_top = name.map(|name| !settings.force_to_top.contains(name)); // `false` < `true` so we get forced to top first - let maybe_length = settings - .length_sort + + let maybe_length = (settings.length_sort + || (settings.length_sort_straight && straight_import)) .then_some(name.map(str::len).unwrap_or_default()); + let maybe_lowercase_name = name.and_then(|name| { (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))) }); + let module_name = name.map(NatOrdStr::from); + let asname = asname.map(NatOrdStr::from); + let first_alias = first_alias.map(|(name, asname)| MemberKey::from_member(name, asname, settings)); diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index e4ec61b738646..b0100cc53bd4f 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2070,6 +2070,16 @@ pub struct IsortOptions { "# )] pub length_sort: Option, + + /// Sort straight imports by their string length. Does not affect from imports. + #[option( + default = r#"false"#, + value_type = "bool", + example = r#" + length-sort-straight = true + "# + )] + pub length_sort_straight: Option, } impl IsortOptions { @@ -2245,6 +2255,7 @@ impl IsortOptions { no_sections, from_first, length_sort: self.length_sort.unwrap_or(false), + length_sort_straight: self.length_sort_straight.unwrap_or(false), }) } } diff --git a/ruff.schema.json b/ruff.schema.json index f9c1af7a9b9e2..25db1b1fa44af 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1484,6 +1484,13 @@ "null" ] }, + "length-sort-straight": { + "description": "Sort straight imports by their string length. Does not affect from imports.", + "type": [ + "boolean", + "null" + ] + }, "lines-after-imports": { "description": "The number of blank lines to place after imports. Use `-1` for automatic determination.\n\nWhen using the formatter, only the values `-1`, `1`, and `2` are compatible because it enforces at least one empty and at most two empty lines after imports.", "type": [ From 0cefa558ec49b0dda3db94f05ca83bff2245446c Mon Sep 17 00:00:00 2001 From: bluthej Date: Sat, 4 Nov 2023 11:23:04 +0100 Subject: [PATCH 3/8] Use `unicode_width` to sort by length + add tests The `UnicodeWidthStr` trait yields "displayed width of Unicode strings". Some tests with non-ASCII characters were added. --- .../isort/length_sort_non_ascii_members.py | 11 ++++++ .../isort/length_sort_non_ascii_modules.py | 9 +++++ .../isort/length_sort_with_star_import.py | 3 ++ crates/ruff_linter/src/rules/isort/mod.rs | 2 + ...ort__length_sort_non_ascii_members.py.snap | 37 +++++++++++++++++++ ...ort__length_sort_non_ascii_modules.py.snap | 32 ++++++++++++++++ crates/ruff_linter/src/rules/isort/sorting.rs | 5 ++- 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_members.py create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_modules.py create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_star_import.py create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_members.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_members.py new file mode 100644 index 0000000000000..6419d6251196c --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_members.py @@ -0,0 +1,11 @@ +from module1 import ( + loooooooooooooong, + σηορτ, + mediuuuuum, + shoort, + looooooooooooooong, + μεδιυυυυυμ, + short, + mediuuuuuum, + λοοοοοοοοοοοοοονγ, +) diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_modules.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_modules.py new file mode 100644 index 0000000000000..e7895a0d2becb --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_non_ascii_modules.py @@ -0,0 +1,9 @@ +import loooooooooooooong +import mediuuuuuum +import short +import σηορτ +import shoort +import mediuuuuum +import λοοοοοοοοοοοοοονγ +import μεδιυυυυυμ +import looooooooooooooong diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_star_import.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_star_import.py new file mode 100644 index 0000000000000..3dbf73c4d2974 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_star_import.py @@ -0,0 +1,3 @@ +from looooooooooooooong import a +from mediuuuum import * +from short import * diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index bee212011e921..1e853883941c6 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -1142,6 +1142,8 @@ mod tests { #[test_case(Path::new("length_sort_straight_imports.py"))] #[test_case(Path::new("length_sort_from_imports.py"))] #[test_case(Path::new("length_sort_straight_and_from_imports.py"))] + #[test_case(Path::new("length_sort_non_ascii_members.py"))] + #[test_case(Path::new("length_sort_non_ascii_modules.py"))] fn length_sort(path: &Path) -> Result<()> { let snapshot = format!("length_sort__{}", path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap new file mode 100644 index 0000000000000..8857649ead049 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap @@ -0,0 +1,37 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_non_ascii_members.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | + 1 | / from module1 import ( + 2 | | loooooooooooooong, + 3 | | σηορτ, + 4 | | mediuuuuum, + 5 | | shoort, + 6 | | looooooooooooooong, + 7 | | μεδιυυυυυμ, + 8 | | short, + 9 | | mediuuuuuum, +10 | | λοοοοοοοοοοοοοονγ, +11 | | ) + | + = help: Organize imports + +ℹ Fix +1 1 | from module1 import ( +2 |- loooooooooooooong, + 2 |+ short, +3 3 | σηορτ, + 4 |+ shoort, +4 5 | mediuuuuum, +5 |- shoort, +6 |- looooooooooooooong, +7 6 | μεδιυυυυυμ, +8 |- short, +9 7 | mediuuuuuum, + 8 |+ loooooooooooooong, +10 9 | λοοοοοοοοοοοοοονγ, + 10 |+ looooooooooooooong, +11 11 | ) + + diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap new file mode 100644 index 0000000000000..469c9f5dfe558 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap @@ -0,0 +1,32 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_non_ascii_modules.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / import loooooooooooooong +2 | | import mediuuuuuum +3 | | import short +4 | | import σηορτ +5 | | import shoort +6 | | import mediuuuuum +7 | | import λοοοοοοοοοοοοοονγ +8 | | import μεδιυυυυυμ +9 | | import looooooooooooooong + | + = help: Organize imports + +ℹ Fix +1 |-import loooooooooooooong +2 |-import mediuuuuuum +3 1 | import short +4 2 | import σηορτ +5 3 | import shoort +6 4 | import mediuuuuum + 5 |+import μεδιυυυυυμ + 6 |+import mediuuuuuum + 7 |+import loooooooooooooong +7 8 | import λοοοοοοοοοοοοοονγ +8 |-import μεδιυυυυυμ +9 9 | import looooooooooooooong + + diff --git a/crates/ruff_linter/src/rules/isort/sorting.rs b/crates/ruff_linter/src/rules/isort/sorting.rs index b85e0d37c7421..7829d38bace4a 100644 --- a/crates/ruff_linter/src/rules/isort/sorting.rs +++ b/crates/ruff_linter/src/rules/isort/sorting.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, cmp::Ordering, cmp::Reverse}; use natord; +use unicode_width::UnicodeWidthStr; use ruff_python_stdlib::str; @@ -103,7 +104,7 @@ impl<'a> ModuleKey<'a> { let maybe_length = (settings.length_sort || (settings.length_sort_straight && straight_import)) - .then_some(name.map(str::len).unwrap_or_default()); + .then_some(name.map(str::width).unwrap_or_default()); let maybe_lowercase_name = name.and_then(|name| { (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))) @@ -146,7 +147,7 @@ impl<'a> MemberKey<'a> { let member_type = settings .order_by_type .then_some(member_type(name, settings)); - let maybe_length = settings.length_sort.then_some(name.len()); + let maybe_length = settings.length_sort.then_some(name.width()); let maybe_lowercase_name = (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))); let module_name = NatOrdStr::from(name); From 3cdc57c5a496d943e99b79acc3247bce454ab425 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 26 Nov 2023 17:45:24 +0100 Subject: [PATCH 4/8] Update test diffs following changes in #8541 --- ..._isort__tests__length_sort__length_sort_from_imports.py.snap | 2 +- ...t__tests__length_sort__length_sort_non_ascii_members.py.snap | 2 +- ...t__tests__length_sort__length_sort_non_ascii_modules.py.snap | 2 +- ...__length_sort__length_sort_straight_and_from_imports.py.snap | 2 +- ...rt__tests__length_sort__length_sort_straight_imports.py.snap | 2 +- ...ests__length_sort_straight__length_sort_from_imports.py.snap | 2 +- ...sort_straight__length_sort_straight_and_from_imports.py.snap | 2 +- ...__length_sort_straight__length_sort_straight_imports.py.snap | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap index 8ed06ce6feb40..a2183e7de32c7 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_from_imports.py.snap @@ -9,7 +9,7 @@ length_sort_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-format | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |+from short import b 1 2 | from mediuuuuuuuuuuum import a 2 |-from short import b diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap index 8857649ead049..d6cfc0e340920 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_members.py.snap @@ -17,7 +17,7 @@ length_sort_non_ascii_members.py:1:1: I001 [*] Import block is un-sorted or un-f | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 1 | from module1 import ( 2 |- loooooooooooooong, 2 |+ short, diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap index 469c9f5dfe558..e1d66bd9a7bb9 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_non_ascii_modules.py.snap @@ -15,7 +15,7 @@ length_sort_non_ascii_modules.py:1:1: I001 [*] Import block is un-sorted or un-f | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |-import loooooooooooooong 2 |-import mediuuuuuum 3 1 | import short diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap index 21596b0e6bd87..7acc627804180 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_and_from_imports.py.snap @@ -12,7 +12,7 @@ length_sort_straight_and_from_imports.py:1:1: I001 [*] Import block is un-sorted | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |+import short 1 2 | import mediuuuuuum 2 |-import short diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap index ef1efbb81750e..6c73af3a2a8fa 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_straight_imports.py.snap @@ -10,7 +10,7 @@ length_sort_straight_imports.py:1:1: I001 [*] Import block is un-sorted or un-fo | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |+import short 2 |+import mediuuuuuuma 1 3 | import mediuuuuuumb diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap index fdd3e96fbd816..35bdec18f120c 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_from_imports.py.snap @@ -9,7 +9,7 @@ length_sort_from_imports.py:1:1: I001 [*] Import block is un-sorted or un-format | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |+from loooooooooooooooooooooog import c 1 2 | from mediuuuuuuuuuuum import a 2 3 | from short import b diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap index 2329b0d47343c..3cd80a04effd9 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_and_from_imports.py.snap @@ -12,7 +12,7 @@ length_sort_straight_and_from_imports.py:1:1: I001 [*] Import block is un-sorted | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |+import short 1 2 | import mediuuuuuum 2 |-import short diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap index ef1efbb81750e..6c73af3a2a8fa 100644 --- a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort_straight__length_sort_straight_imports.py.snap @@ -10,7 +10,7 @@ length_sort_straight_imports.py:1:1: I001 [*] Import block is un-sorted or un-fo | = help: Organize imports -ℹ Fix +ℹ Safe fix 1 |+import short 2 |+import mediuuuuuuma 1 3 | import mediuuuuuumb From 11fa9b42c7910e514478a2766a8fde860de9dda2 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 26 Nov 2023 18:09:47 +0100 Subject: [PATCH 5/8] Reorder members of the module key - Makes more sense to have force_to_top first - Allows to place maybe_length after force_to_top but before distance --- crates/ruff_linter/src/rules/isort/sorting.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/isort/sorting.rs b/crates/ruff_linter/src/rules/isort/sorting.rs index 7829d38bace4a..e03eba6cead8a 100644 --- a/crates/ruff_linter/src/rules/isort/sorting.rs +++ b/crates/ruff_linter/src/rules/isort/sorting.rs @@ -75,9 +75,9 @@ pub(crate) enum Distance { /// `foo` in `from foo import bar`). #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)] pub(crate) struct ModuleKey<'a> { - distance: Distance, - force_to_top: Option, + force_to_top: bool, maybe_length: Option, + distance: Distance, maybe_lowercase_name: Option>, module_name: Option>, first_alias: Option>, @@ -93,6 +93,14 @@ impl<'a> ModuleKey<'a> { settings: &Settings, straight_import: bool, ) -> Self { + let force_to_top = !name + .map(|name| settings.force_to_top.contains(name)) + .unwrap_or_default(); // `false` < `true` so we get forced to top first + + let maybe_length = (settings.length_sort + || (settings.length_sort_straight && straight_import)) + .then_some(name.map(str::width).unwrap_or_default()); + let distance = match settings.relative_imports_order { RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level.unwrap_or_default()), RelativeImportsOrder::FurthestToClosest => { @@ -100,12 +108,6 @@ impl<'a> ModuleKey<'a> { } }; - let force_to_top = name.map(|name| !settings.force_to_top.contains(name)); // `false` < `true` so we get forced to top first - - let maybe_length = (settings.length_sort - || (settings.length_sort_straight && straight_import)) - .then_some(name.map(str::width).unwrap_or_default()); - let maybe_lowercase_name = name.and_then(|name| { (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))) }); @@ -118,9 +120,9 @@ impl<'a> ModuleKey<'a> { first_alias.map(|(name, asname)| MemberKey::from_member(name, asname, settings)); Self { - distance, force_to_top, maybe_length, + distance, maybe_lowercase_name, module_name, first_alias, From 14f01360951bcce403a4908f5ec261c979db78fd Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 26 Nov 2023 18:21:05 +0100 Subject: [PATCH 6/8] Add test + count level (nb of dots) in length The name does not contain the dots for relative imports so the number of dots has to be added --- .../length_sort_with_relative_imports.py | 7 +++++ crates/ruff_linter/src/rules/isort/mod.rs | 1 + ..._length_sort_with_relative_imports.py.snap | 28 +++++++++++++++++++ crates/ruff_linter/src/rules/isort/sorting.rs | 10 +++---- 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_relative_imports.py create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_with_relative_imports.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_relative_imports.py b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_relative_imports.py new file mode 100644 index 0000000000000..ca0b97065ecad --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/length_sort_with_relative_imports.py @@ -0,0 +1,7 @@ +from ..looooooooooooooong import a +from ...mediuuuum import b +from .short import c +from ....short import c +from . import d +from .mediuuuum import a +from ......short import b diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index 1e853883941c6..60b511847c909 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -1144,6 +1144,7 @@ mod tests { #[test_case(Path::new("length_sort_straight_and_from_imports.py"))] #[test_case(Path::new("length_sort_non_ascii_members.py"))] #[test_case(Path::new("length_sort_non_ascii_modules.py"))] + #[test_case(Path::new("length_sort_with_relative_imports.py"))] fn length_sort(path: &Path) -> Result<()> { let snapshot = format!("length_sort__{}", path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_with_relative_imports.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_with_relative_imports.py.snap new file mode 100644 index 0000000000000..20531ecd26d43 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__length_sort_with_relative_imports.py.snap @@ -0,0 +1,28 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +length_sort_with_relative_imports.py:1:1: I001 [*] Import block is un-sorted or un-formatted + | +1 | / from ..looooooooooooooong import a +2 | | from ...mediuuuum import b +3 | | from .short import c +4 | | from ....short import c +5 | | from . import d +6 | | from .mediuuuum import a +7 | | from ......short import b + | + = help: Organize imports + +ℹ Safe fix +1 |-from ..looooooooooooooong import a +2 |-from ...mediuuuum import b + 1 |+from . import d +3 2 | from .short import c +4 3 | from ....short import c +5 |-from . import d +6 4 | from .mediuuuum import a +7 5 | from ......short import b + 6 |+from ...mediuuuum import b + 7 |+from ..looooooooooooooong import a + + diff --git a/crates/ruff_linter/src/rules/isort/sorting.rs b/crates/ruff_linter/src/rules/isort/sorting.rs index e03eba6cead8a..97d65938127d7 100644 --- a/crates/ruff_linter/src/rules/isort/sorting.rs +++ b/crates/ruff_linter/src/rules/isort/sorting.rs @@ -93,19 +93,19 @@ impl<'a> ModuleKey<'a> { settings: &Settings, straight_import: bool, ) -> Self { + let level = level.unwrap_or_default(); + let force_to_top = !name .map(|name| settings.force_to_top.contains(name)) .unwrap_or_default(); // `false` < `true` so we get forced to top first let maybe_length = (settings.length_sort || (settings.length_sort_straight && straight_import)) - .then_some(name.map(str::width).unwrap_or_default()); + .then_some(name.map(str::width).unwrap_or_default() + level as usize); let distance = match settings.relative_imports_order { - RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level.unwrap_or_default()), - RelativeImportsOrder::FurthestToClosest => { - Distance::Furthest(Reverse(level.unwrap_or_default())) - } + RelativeImportsOrder::ClosestToFurthest => Distance::Nearest(level), + RelativeImportsOrder::FurthestToClosest => Distance::Furthest(Reverse(level)), }; let maybe_lowercase_name = name.and_then(|name| { From 06110bfaa993fca0ceb74f7b4b9f2366f27588b6 Mon Sep 17 00:00:00 2001 From: bluthej Date: Sun, 26 Nov 2023 18:35:35 +0100 Subject: [PATCH 7/8] Add detail in the documentation --- crates/ruff_workspace/src/options.rs | 7 ++++++- ruff.schema.json | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index b0100cc53bd4f..e9afcd9389336 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2062,6 +2062,9 @@ pub struct IsortOptions { pub sections: Option>>, /// Sort imports by their string length. + /// + /// The string length is computed using the [`unicode_width`](https://crates.io/crates/unicode-width) crate, + /// i.e. it is the displayed width of the string according to [Unicode Standard Annex #11 rules](http://www.unicode.org/reports/tr11/). #[option( default = r#"false"#, value_type = "bool", @@ -2071,7 +2074,9 @@ pub struct IsortOptions { )] pub length_sort: Option, - /// Sort straight imports by their string length. Does not affect from imports. + /// Sort straight imports by their string length. + /// + /// Same as `length-sort` but does not affect from imports. #[option( default = r#"false"#, value_type = "bool", diff --git a/ruff.schema.json b/ruff.schema.json index 25db1b1fa44af..11dc89e015b30 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1478,14 +1478,14 @@ } }, "length-sort": { - "description": "Sort imports by their string length.", + "description": "Sort imports by their string length.\n\nThe string length is computed using the [`unicode_width`](https://crates.io/crates/unicode-width) crate, i.e. it is the displayed width of the string according to [Unicode Standard Annex #11 rules](http://www.unicode.org/reports/tr11/).", "type": [ "boolean", "null" ] }, "length-sort-straight": { - "description": "Sort straight imports by their string length. Does not affect from imports.", + "description": "Sort straight imports by their string length.\n\nSame as `length-sort` but does not affect from imports.", "type": [ "boolean", "null" From 61c0b88723d77f5f8c761917a5101f6bf3a31334 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 27 Nov 2023 21:48:43 -0800 Subject: [PATCH 8/8] Use enum; tweak docs --- crates/ruff_linter/src/rules/isort/order.rs | 16 ++++-- crates/ruff_linter/src/rules/isort/sorting.rs | 14 ++++-- crates/ruff_workspace/src/options.rs | 49 +++++++++++-------- ruff.schema.json | 4 +- 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/crates/ruff_linter/src/rules/isort/order.rs b/crates/ruff_linter/src/rules/isort/order.rs index 01f8362a7ffec..25430b84ad6f9 100644 --- a/crates/ruff_linter/src/rules/isort/order.rs +++ b/crates/ruff_linter/src/rules/isort/order.rs @@ -1,3 +1,4 @@ +use crate::rules::isort::sorting::ImportStyle; use itertools::Itertools; use super::settings::Settings; @@ -61,22 +62,29 @@ pub(crate) fn order_imports<'a>( alias.asname, None, None, + ImportStyle::Straight, settings, - true, ), ImportFrom((import_from, _, _, aliases)) => ModuleKey::from_module( import_from.module, None, import_from.level, aliases.first().map(|(alias, _)| (alias.name, alias.asname)), + ImportStyle::From, settings, - false, ), }) .collect() } else { let ordered_straight_imports = straight_imports.sorted_by_cached_key(|(alias, _)| { - ModuleKey::from_module(Some(alias.name), alias.asname, None, None, settings, true) + ModuleKey::from_module( + Some(alias.name), + alias.asname, + None, + None, + ImportStyle::Straight, + settings, + ) }); let ordered_from_imports = from_imports.sorted_by_cached_key(|(import_from, _, _, aliases)| { @@ -85,8 +93,8 @@ pub(crate) fn order_imports<'a>( None, import_from.level, aliases.first().map(|(alias, _)| (alias.name, alias.asname)), + ImportStyle::From, settings, - false, ) }); if settings.from_first { diff --git a/crates/ruff_linter/src/rules/isort/sorting.rs b/crates/ruff_linter/src/rules/isort/sorting.rs index 97d65938127d7..aa979fc90c89e 100644 --- a/crates/ruff_linter/src/rules/isort/sorting.rs +++ b/crates/ruff_linter/src/rules/isort/sorting.rs @@ -65,12 +65,20 @@ impl<'a> From for NatOrdStr<'a> { } } -#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] pub(crate) enum Distance { Nearest(u32), Furthest(Reverse), } +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub(crate) enum ImportStyle { + // Ex) `import foo` + Straight, + // Ex) `from foo import bar` + From, +} + /// A comparable key to capture the desired sorting order for an imported module (e.g., /// `foo` in `from foo import bar`). #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)] @@ -90,8 +98,8 @@ impl<'a> ModuleKey<'a> { asname: Option<&'a str>, level: Option, first_alias: Option<(&'a str, Option<&'a str>)>, + style: ImportStyle, settings: &Settings, - straight_import: bool, ) -> Self { let level = level.unwrap_or_default(); @@ -100,7 +108,7 @@ impl<'a> ModuleKey<'a> { .unwrap_or_default(); // `false` < `true` so we get forced to top first let maybe_length = (settings.length_sort - || (settings.length_sort_straight && straight_import)) + || (settings.length_sort_straight && style == ImportStyle::Straight)) .then_some(name.map(str::width).unwrap_or_default() + level as usize); let distance = match settings.relative_imports_order { diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index e9afcd9389336..05f4522710cd9 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2047,24 +2047,20 @@ pub struct IsortOptions { )] pub from_first: Option, - // Tables are required to go last. - /// A list of mappings from section names to modules. - /// By default custom sections are output last, but this can be overridden with `section-order`. - #[option( - default = "{}", - value_type = "dict[str, list[str]]", - scope = "sections", - example = r#" - # Group all Django imports into a separate section. - "django" = ["django"] - "# - )] - pub sections: Option>>, - - /// Sort imports by their string length. + /// Sort imports by their string length, such that shorter imports appear + /// before longer imports. For example, by default, imports will be sorted + /// alphabetically, as in: + /// ```python + /// import collections + /// import os + /// ``` /// - /// The string length is computed using the [`unicode_width`](https://crates.io/crates/unicode-width) crate, - /// i.e. it is the displayed width of the string according to [Unicode Standard Annex #11 rules](http://www.unicode.org/reports/tr11/). + /// Setting `length-sort = true` will instead sort such that shorter imports + /// appear before longer imports, as in: + /// ```python + /// import os + /// import collections + /// ``` #[option( default = r#"false"#, value_type = "bool", @@ -2074,9 +2070,8 @@ pub struct IsortOptions { )] pub length_sort: Option, - /// Sort straight imports by their string length. - /// - /// Same as `length-sort` but does not affect from imports. + /// Sort straight imports by their string length. Similar to `length-sort`, + /// but applies only to straight imports and doesn't affect `from` imports. #[option( default = r#"false"#, value_type = "bool", @@ -2085,6 +2080,20 @@ pub struct IsortOptions { "# )] pub length_sort_straight: Option, + + // Tables are required to go last. + /// A list of mappings from section names to modules. + /// By default custom sections are output last, but this can be overridden with `section-order`. + #[option( + default = "{}", + value_type = "dict[str, list[str]]", + scope = "sections", + example = r#" + # Group all Django imports into a separate section. + "django" = ["django"] + "# + )] + pub sections: Option>>, } impl IsortOptions { diff --git a/ruff.schema.json b/ruff.schema.json index 11dc89e015b30..d5e7f0c0acc31 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1478,14 +1478,14 @@ } }, "length-sort": { - "description": "Sort imports by their string length.\n\nThe string length is computed using the [`unicode_width`](https://crates.io/crates/unicode-width) crate, i.e. it is the displayed width of the string according to [Unicode Standard Annex #11 rules](http://www.unicode.org/reports/tr11/).", + "description": "Sort imports by their string length, such that shorter imports appear before longer imports. For example, by default, imports will be sorted alphabetically, as in: ```python import collections import os ```\n\nSetting `length-sort = true` will instead sort such that shorter imports appear before longer imports, as in: ```python import os import collections ```", "type": [ "boolean", "null" ] }, "length-sort-straight": { - "description": "Sort straight imports by their string length.\n\nSame as `length-sort` but does not affect from imports.", + "description": "Sort straight imports by their string length. Similar to `length-sort`, but applies only to straight imports and doesn't affect `from` imports.", "type": [ "boolean", "null"