diff --git a/src/isort/mod.rs b/src/isort/mod.rs index aaa136086df13..db104360e6cc3 100644 --- a/src/isort/mod.rs +++ b/src/isort/mod.rs @@ -399,7 +399,7 @@ fn categorize_imports<'a>( block_by_type } -fn sort_imports(block: ImportBlock) -> OrderedImportBlock { +fn sort_imports(block: ImportBlock, order_by_type: bool) -> OrderedImportBlock { let mut ordered = OrderedImportBlock::default(); // Sort `StmtKind::Import`. @@ -477,7 +477,9 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock { locations, aliases .into_iter() - .sorted_by(|(alias1, _), (alias2, _)| cmp_members(alias1, alias2)) + .sorted_by(|(alias1, _), (alias2, _)| { + cmp_members(alias1, alias2, order_by_type) + }) .collect::>(), ) }) @@ -488,7 +490,9 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock { (None, None) => Ordering::Equal, (None, Some(_)) => Ordering::Less, (Some(_), None) => Ordering::Greater, - (Some((alias1, _)), Some((alias2, _))) => cmp_members(alias1, alias2), + (Some((alias1, _)), Some((alias2, _))) => { + cmp_members(alias1, alias2, order_by_type) + } } }) }, @@ -561,6 +565,7 @@ pub fn format_imports( split_on_trailing_comma: bool, force_single_line: bool, single_line_exclusions: &BTreeSet, + order_by_type: bool, ) -> String { let trailer = &block.trailer; let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma); @@ -583,7 +588,7 @@ pub fn format_imports( // Generate replacement source code. let mut is_first_block = true; for import_block in block_by_type.into_values() { - let mut import_block = sort_imports(import_block); + let mut import_block = sort_imports(import_block, order_by_type); if force_single_line { import_block = force_single_line_imports(import_block, single_line_exclusions); } @@ -781,4 +786,25 @@ mod tests { insta::assert_yaml_snapshot!(snapshot, checks); Ok(()) } + + #[test_case(Path::new("order_by_type.py"))] + fn order_by_type(path: &Path) -> Result<()> { + let snapshot = format!("order_by_type_false_{}", path.to_string_lossy()); + let mut checks = test_path( + Path::new("./resources/test/fixtures/isort") + .join(path) + .as_path(), + &Settings { + isort: isort::settings::Settings { + order_by_type: false, + ..isort::settings::Settings::default() + }, + src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()], + ..Settings::for_rule(CheckCode::I001) + }, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } } diff --git a/src/isort/plugins.rs b/src/isort/plugins.rs index da03e5b8e487d..fbf67a3d69746 100644 --- a/src/isort/plugins.rs +++ b/src/isort/plugins.rs @@ -81,6 +81,7 @@ pub fn check_imports( settings.isort.split_on_trailing_comma, settings.isort.force_single_line, &settings.isort.single_line_exclusions, + settings.isort.order_by_type, ); // Expand the span the entire range, including leading and trailing space. diff --git a/src/isort/settings.rs b/src/isort/settings.rs index fb58edaecec77..82d2668984230 100644 --- a/src/isort/settings.rs +++ b/src/isort/settings.rs @@ -78,6 +78,16 @@ pub struct Options { /// /// See isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option. pub split_on_trailing_comma: Option, + #[option( + default = r#"true"#, + value_type = "bool", + example = r#" + order-by-type = true + "# + )] + /// Order imports by type, which is determined by case, in addition to + /// alphabetically. + pub order_by_type: Option, #[option( default = r#"[]"#, value_type = "Vec", @@ -120,6 +130,7 @@ pub struct Settings { pub single_line_exclusions: BTreeSet, pub known_first_party: BTreeSet, pub known_third_party: BTreeSet, + pub order_by_type: bool, pub extra_standard_library: BTreeSet, } @@ -130,6 +141,7 @@ impl Default for Settings { force_wrap_aliases: false, split_on_trailing_comma: true, force_single_line: false, + order_by_type: true, single_line_exclusions: BTreeSet::new(), known_first_party: BTreeSet::new(), known_third_party: BTreeSet::new(), @@ -145,6 +157,7 @@ impl From for Settings { force_wrap_aliases: options.force_wrap_aliases.unwrap_or(false), split_on_trailing_comma: options.split_on_trailing_comma.unwrap_or(true), force_single_line: options.force_single_line.unwrap_or(false), + order_by_type: options.order_by_type.unwrap_or(true), single_line_exclusions: BTreeSet::from_iter( options.single_line_exclusions.unwrap_or_default(), ), @@ -164,6 +177,7 @@ impl From for Options { force_wrap_aliases: Some(settings.force_wrap_aliases), split_on_trailing_comma: Some(settings.split_on_trailing_comma), force_single_line: Some(settings.force_single_line), + order_by_type: Some(settings.order_by_type), single_line_exclusions: Some(settings.single_line_exclusions.into_iter().collect()), known_first_party: Some(settings.known_first_party.into_iter().collect()), known_third_party: Some(settings.known_third_party.into_iter().collect()), diff --git a/src/isort/snapshots/ruff__isort__tests__order_by_type_false_order_by_type.py.snap b/src/isort/snapshots/ruff__isort__tests__order_by_type_false_order_by_type.py.snap new file mode 100644 index 0000000000000..bee4aa4091458 --- /dev/null +++ b/src/isort/snapshots/ruff__isort__tests__order_by_type_false_order_by_type.py.snap @@ -0,0 +1,21 @@ +--- +source: src/isort/mod.rs +expression: checks +--- +- kind: UnsortedImports + location: + row: 1 + column: 0 + end_location: + row: 13 + column: 0 + fix: + content: "import glob\nimport os\nimport shutil\nimport tempfile\nimport time\nfrom subprocess import PIPE, Popen, STDOUT\n\nimport BAR\nimport bar\nimport FOO\nimport foo\nimport StringIO\nfrom module import Apple, BASIC, Class, CONSTANT, function\n" + location: + row: 1 + column: 0 + end_location: + row: 13 + column: 0 + parent: ~ + diff --git a/src/isort/sorting.rs b/src/isort/sorting.rs index 6d8b3d4192864..aac29ba0e2ed5 100644 --- a/src/isort/sorting.rs +++ b/src/isort/sorting.rs @@ -11,8 +11,10 @@ pub enum Prefix { Variables, } -fn prefix(name: &str) -> Prefix { - if name.len() > 1 && string::is_upper(name) { +fn prefix(name: &str, order_by_type: bool) -> Prefix { + if !order_by_type { + Prefix::Variables + } else if name.len() > 1 && string::is_upper(name) { // Ex) `CONSTANT` Prefix::Constants } else if name.chars().next().map_or(false, char::is_uppercase) { @@ -37,9 +39,9 @@ pub fn cmp_modules(alias1: &AliasData, alias2: &AliasData) -> Ordering { } /// Compare two member imports within `StmtKind::ImportFrom` blocks. -pub fn cmp_members(alias1: &AliasData, alias2: &AliasData) -> Ordering { - prefix(alias1.name) - .cmp(&prefix(alias2.name)) +pub fn cmp_members(alias1: &AliasData, alias2: &AliasData, order_by_type: bool) -> Ordering { + prefix(alias1.name, order_by_type) + .cmp(&prefix(alias2.name, order_by_type)) .then_with(|| cmp_modules(alias1, alias2)) }