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_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_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/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/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 5fce5a86acfc8..60b511847c909 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -1138,4 +1138,47 @@ 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"))] + #[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( + 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(()) + } + + #[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..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; @@ -56,21 +57,34 @@ 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, + ImportStyle::Straight, + settings, + ), 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, ), }) .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, + ImportStyle::Straight, + settings, + ) }); let ordered_from_imports = from_imports.sorted_by_cached_key(|(import_from, _, _, aliases)| { @@ -79,6 +93,7 @@ pub(crate) fn order_imports<'a>( None, import_from.level, aliases.first().map(|(alias, _)| (alias.name, alias.asname)), + ImportStyle::From, settings, ) }); diff --git a/crates/ruff_linter/src/rules/isort/settings.rs b/crates/ruff_linter/src/rules/isort/settings.rs index d4937520fa7aa..2a28a3c8b396b 100644 --- a/crates/ruff_linter/src/rules/isort/settings.rs +++ b/crates/ruff_linter/src/rules/isort/settings.rs @@ -58,6 +58,8 @@ pub struct Settings { pub section_order: Vec, pub no_sections: bool, pub from_first: bool, + pub length_sort: bool, + pub length_sort_straight: bool, } impl Default for Settings { @@ -86,6 +88,8 @@ impl Default for Settings { section_order: ImportType::iter().map(ImportSection::Known).collect(), 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__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 new file mode 100644 index 0000000000000..a2183e7de32c7 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__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 + +ℹ Safe 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__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..d6cfc0e340920 --- /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 + +ℹ Safe 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..e1d66bd9a7bb9 --- /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 + +ℹ Safe 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/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 new file mode 100644 index 0000000000000..7acc627804180 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__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 + +ℹ Safe 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__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 new file mode 100644 index 0000000000000..6c73af3a2a8fa --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__length_sort__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 + +ℹ Safe 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/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/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..35bdec18f120c --- /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 + +ℹ Safe 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..3cd80a04effd9 --- /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 + +ℹ Safe 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..6c73af3a2a8fa --- /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 + +ℹ Safe 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..aa979fc90c89e 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; @@ -64,18 +65,27 @@ 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)] pub(crate) struct ModuleKey<'a> { + force_to_top: bool, + maybe_length: Option, distance: Distance, - force_to_top: Option, maybe_lowercase_name: Option>, module_name: Option>, first_alias: Option>, @@ -88,26 +98,39 @@ impl<'a> ModuleKey<'a> { asname: Option<&'a str>, level: Option, first_alias: Option<(&'a str, Option<&'a str>)>, + style: ImportStyle, settings: &Settings, ) -> 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 && style == ImportStyle::Straight)) + .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 force_to_top = name.map(|name| !settings.force_to_top.contains(name)); // `false` < `true` so we get forced to top first + 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)); Self { - distance, force_to_top, + maybe_length, + distance, maybe_lowercase_name, module_name, first_alias, @@ -122,6 +145,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 +157,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.width()); let maybe_lowercase_name = (!settings.case_sensitive).then_some(NatOrdStr(maybe_lowercase(name))); let module_name = NatOrdStr::from(name); @@ -141,6 +166,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..05f4522710cd9 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2047,6 +2047,40 @@ pub struct IsortOptions { )] pub from_first: Option, + /// 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 + /// ``` + /// + /// 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", + example = r#" + length-sort = true + "# + )] + pub length_sort: Option, + + /// 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", + example = r#" + length-sort-straight = true + "# + )] + 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`. @@ -2234,6 +2268,8 @@ impl IsortOptions { section_order, 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 8ae7c3fe37fb2..d5e7f0c0acc31 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1477,6 +1477,20 @@ "type": "string" } }, + "length-sort": { + "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. Similar to `length-sort`, but applies only to straight imports and doesn't 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": [