Skip to content

Commit

Permalink
Add isort.order-by-type boolean setting
Browse files Browse the repository at this point in the history
The default `isort` behavior is `order-by-type = true`.
When imports are ordered by type:

1. `CONSTANT_VARIABLES` are first.
2. `CamelCaseClasses` are second.
3. `everything_else` is third.

- https://pycqa.github.io/isort/docs/configuration/options.html#order-by-type

When `order-by-type = false` imports are ordered alphabetically (case-insensitive).

eg.

`order-by-type = false`

```py
import BAR
import bar
import FOO
import foo
import StringIO
from module import Apple, BASIC, Class, CONSTANT, function
```

`order-by-type = true`

```py
import BAR
import bar
import FOO
import foo
import StringIO
from module import BASIC, CONSTANT, Apple, Class, function
```
  • Loading branch information
mattoberle committed Jan 3, 2023
1 parent 8d99e31 commit 86b17f7
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 9 deletions.
34 changes: 30 additions & 4 deletions src/isort/mod.rs
Expand Up @@ -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`.
Expand Down Expand Up @@ -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::<Vec<(AliasData, CommentSet)>>(),
)
})
Expand All @@ -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)
}
}
})
},
Expand Down Expand Up @@ -561,6 +565,7 @@ pub fn format_imports(
split_on_trailing_comma: bool,
force_single_line: bool,
single_line_exclusions: &BTreeSet<String>,
order_by_type: bool,
) -> String {
let trailer = &block.trailer;
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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(())
}
}
1 change: 1 addition & 0 deletions src/isort/plugins.rs
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions src/isort/settings.rs
Expand Up @@ -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<bool>,
#[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<bool>,
#[option(
default = r#"[]"#,
value_type = "Vec<String>",
Expand Down Expand Up @@ -120,6 +130,7 @@ pub struct Settings {
pub single_line_exclusions: BTreeSet<String>,
pub known_first_party: BTreeSet<String>,
pub known_third_party: BTreeSet<String>,
pub order_by_type: bool,
pub extra_standard_library: BTreeSet<String>,
}

Expand All @@ -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(),
Expand All @@ -145,6 +157,7 @@ impl From<Options> 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(),
),
Expand All @@ -164,6 +177,7 @@ impl From<Settings> 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()),
Expand Down
@@ -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: ~

12 changes: 7 additions & 5 deletions src/isort/sorting.rs
Expand Up @@ -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) {
Expand All @@ -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))
}

Expand Down

0 comments on commit 86b17f7

Please sign in to comment.