mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:35 +00:00
[isort]
Add --case-sensitive
flag (#5539)
## Summary Adds a `--case-sensitive` setting/flag to isort (default: `false`) which, when set to `true` sorts imports case sensitively instead of case insensitively. Tests and Docs can be improved, can do that if the general idea of the implementation is in order. First `isort` edit so any and all feedback is welcomed even more than usual. ## Test Plan Added a fixture with an assortment of imports in various cases. ## Issue links Closes: https://github.com/astral-sh/ruff/issues/5514
This commit is contained in:
parent
5a74a8e5a1
commit
6f548d9872
8 changed files with 134 additions and 12 deletions
9
crates/ruff/resources/test/fixtures/isort/case_sensitive.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/isort/case_sensitive.py
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
import A
|
||||
import B
|
||||
import b
|
||||
import C
|
||||
import d
|
||||
import E
|
||||
import f
|
||||
from g import a, B, c
|
||||
from h import A, b, C
|
|
@ -74,6 +74,7 @@ pub(crate) fn format_imports(
|
|||
combine_as_imports: bool,
|
||||
force_single_line: bool,
|
||||
force_sort_within_sections: bool,
|
||||
case_sensitive: bool,
|
||||
force_wrap_aliases: bool,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
known_modules: &KnownModules,
|
||||
|
@ -114,6 +115,7 @@ pub(crate) fn format_imports(
|
|||
src,
|
||||
package,
|
||||
force_sort_within_sections,
|
||||
case_sensitive,
|
||||
force_wrap_aliases,
|
||||
force_to_top,
|
||||
known_modules,
|
||||
|
@ -171,6 +173,7 @@ fn format_import_block(
|
|||
src: &[PathBuf],
|
||||
package: Option<&Path>,
|
||||
force_sort_within_sections: bool,
|
||||
case_sensitive: bool,
|
||||
force_wrap_aliases: bool,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
known_modules: &KnownModules,
|
||||
|
@ -206,6 +209,7 @@ fn format_import_block(
|
|||
let imports = order_imports(
|
||||
import_block,
|
||||
order_by_type,
|
||||
case_sensitive,
|
||||
relative_imports_order,
|
||||
classes,
|
||||
constants,
|
||||
|
@ -222,7 +226,13 @@ fn format_import_block(
|
|||
.collect::<Vec<EitherImport>>();
|
||||
if force_sort_within_sections {
|
||||
imports.sort_by(|import1, import2| {
|
||||
cmp_either_import(import1, import2, relative_imports_order, force_to_top)
|
||||
cmp_either_import(
|
||||
import1,
|
||||
import2,
|
||||
relative_imports_order,
|
||||
force_to_top,
|
||||
case_sensitive,
|
||||
)
|
||||
});
|
||||
};
|
||||
imports
|
||||
|
@ -449,6 +459,24 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("case_sensitive.py"))]
|
||||
fn case_sensitive(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("case_sensitive_{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("isort").join(path).as_path(),
|
||||
&Settings {
|
||||
isort: super::settings::Settings {
|
||||
case_sensitive: true,
|
||||
..super::settings::Settings::default()
|
||||
},
|
||||
src: vec![test_resource_path("fixtures/isort")],
|
||||
..Settings::for_rule(Rule::UnsortedImports)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("force_to_top.py"))]
|
||||
fn force_to_top(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("force_to_top_{}", path.to_string_lossy());
|
||||
|
|
|
@ -9,9 +9,11 @@ use super::settings::RelativeImportsOrder;
|
|||
use super::sorting::{cmp_import_from, cmp_members, cmp_modules};
|
||||
use super::types::{AliasData, CommentSet, ImportBlock, OrderedImportBlock};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn order_imports<'a>(
|
||||
block: ImportBlock<'a>,
|
||||
order_by_type: bool,
|
||||
case_sensitive: bool,
|
||||
relative_imports_order: RelativeImportsOrder,
|
||||
classes: &'a BTreeSet<String>,
|
||||
constants: &'a BTreeSet<String>,
|
||||
|
@ -25,7 +27,9 @@ pub(crate) fn order_imports<'a>(
|
|||
block
|
||||
.import
|
||||
.into_iter()
|
||||
.sorted_by(|(alias1, _), (alias2, _)| cmp_modules(alias1, alias2, force_to_top)),
|
||||
.sorted_by(|(alias1, _), (alias2, _)| {
|
||||
cmp_modules(alias1, alias2, force_to_top, case_sensitive)
|
||||
}),
|
||||
);
|
||||
|
||||
// Sort `Stmt::ImportFrom`.
|
||||
|
@ -70,6 +74,7 @@ pub(crate) fn order_imports<'a>(
|
|||
constants,
|
||||
variables,
|
||||
force_to_top,
|
||||
case_sensitive,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(AliasData, CommentSet)>>(),
|
||||
|
@ -83,6 +88,7 @@ pub(crate) fn order_imports<'a>(
|
|||
import_from2,
|
||||
relative_imports_order,
|
||||
force_to_top,
|
||||
case_sensitive,
|
||||
)
|
||||
.then_with(|| match (aliases1.first(), aliases2.first()) {
|
||||
(None, None) => Ordering::Equal,
|
||||
|
@ -96,6 +102,7 @@ pub(crate) fn order_imports<'a>(
|
|||
constants,
|
||||
variables,
|
||||
force_to_top,
|
||||
case_sensitive,
|
||||
),
|
||||
})
|
||||
},
|
||||
|
|
|
@ -127,6 +127,7 @@ pub(crate) fn organize_imports(
|
|||
settings.isort.combine_as_imports,
|
||||
settings.isort.force_single_line,
|
||||
settings.isort.force_sort_within_sections,
|
||||
settings.isort.case_sensitive,
|
||||
settings.isort.force_wrap_aliases,
|
||||
&settings.isort.force_to_top,
|
||||
&settings.isort.known_modules,
|
||||
|
|
|
@ -127,6 +127,15 @@ pub struct Options {
|
|||
/// imports (like `from itertools import groupby`). Instead, sort the
|
||||
/// imports by module, independent of import style.
|
||||
pub force_sort_within_sections: Option<bool>,
|
||||
#[option(
|
||||
default = r#"false"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
case-sensitive = true
|
||||
"#
|
||||
)]
|
||||
/// Sort imports taking into account case sensitivity.
|
||||
pub case_sensitive: Option<bool>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
|
@ -303,6 +312,7 @@ pub struct Settings {
|
|||
pub combine_as_imports: bool,
|
||||
pub force_single_line: bool,
|
||||
pub force_sort_within_sections: bool,
|
||||
pub case_sensitive: bool,
|
||||
pub force_wrap_aliases: bool,
|
||||
pub force_to_top: BTreeSet<String>,
|
||||
pub known_modules: KnownModules,
|
||||
|
@ -327,6 +337,7 @@ impl Default for Settings {
|
|||
combine_as_imports: false,
|
||||
force_single_line: false,
|
||||
force_sort_within_sections: false,
|
||||
case_sensitive: false,
|
||||
force_wrap_aliases: false,
|
||||
force_to_top: BTreeSet::new(),
|
||||
known_modules: KnownModules::default(),
|
||||
|
@ -429,6 +440,7 @@ impl From<Options> for Settings {
|
|||
combine_as_imports: options.combine_as_imports.unwrap_or(false),
|
||||
force_single_line: options.force_single_line.unwrap_or(false),
|
||||
force_sort_within_sections: options.force_sort_within_sections.unwrap_or(false),
|
||||
case_sensitive: options.case_sensitive.unwrap_or(false),
|
||||
force_wrap_aliases: options.force_wrap_aliases.unwrap_or(false),
|
||||
force_to_top: BTreeSet::from_iter(options.force_to_top.unwrap_or_default()),
|
||||
known_modules: KnownModules::new(
|
||||
|
@ -468,6 +480,7 @@ impl From<Settings> for Options {
|
|||
),
|
||||
force_single_line: Some(settings.force_single_line),
|
||||
force_sort_within_sections: Some(settings.force_sort_within_sections),
|
||||
case_sensitive: Some(settings.case_sensitive),
|
||||
force_wrap_aliases: Some(settings.force_wrap_aliases),
|
||||
force_to_top: Some(settings.force_to_top.into_iter().collect()),
|
||||
known_first_party: Some(
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
case_sensitive.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | / import A
|
||||
2 | | import B
|
||||
3 | | import b
|
||||
4 | | import C
|
||||
5 | | import d
|
||||
6 | | import E
|
||||
7 | | import f
|
||||
8 | | from g import a, B, c
|
||||
9 | | from h import A, b, C
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Fix
|
||||
1 1 | import A
|
||||
2 2 | import B
|
||||
3 |+import C
|
||||
4 |+import E
|
||||
3 5 | import b
|
||||
4 |-import C
|
||||
5 6 | import d
|
||||
6 |-import E
|
||||
7 7 | import f
|
||||
8 |-from g import a, B, c
|
||||
9 |-from h import A, b, C
|
||||
8 |+from g import B, a, c
|
||||
9 |+from h import A, C, b
|
||||
|
||||
|
|
@ -56,10 +56,17 @@ pub(crate) fn cmp_modules(
|
|||
alias1: &AliasData,
|
||||
alias2: &AliasData,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
case_sensitive: bool,
|
||||
) -> Ordering {
|
||||
cmp_force_to_top(alias1.name, alias2.name, force_to_top)
|
||||
.then_with(|| natord::compare_ignore_case(alias1.name, alias2.name))
|
||||
.then_with(|| natord::compare(alias1.name, alias2.name))
|
||||
.then_with(|| {
|
||||
if case_sensitive {
|
||||
natord::compare(alias1.name, alias2.name)
|
||||
} else {
|
||||
natord::compare_ignore_case(alias1.name, alias2.name)
|
||||
.then_with(|| natord::compare(alias1.name, alias2.name))
|
||||
}
|
||||
})
|
||||
.then_with(|| match (alias1.asname, alias2.asname) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
|
@ -69,6 +76,7 @@ pub(crate) fn cmp_modules(
|
|||
}
|
||||
|
||||
/// Compare two member imports within `Stmt::ImportFrom` blocks.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn cmp_members(
|
||||
alias1: &AliasData,
|
||||
alias2: &AliasData,
|
||||
|
@ -77,6 +85,7 @@ pub(crate) fn cmp_members(
|
|||
constants: &BTreeSet<String>,
|
||||
variables: &BTreeSet<String>,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
case_sensitive: bool,
|
||||
) -> Ordering {
|
||||
match (alias1.name == "*", alias2.name == "*") {
|
||||
(true, false) => Ordering::Less,
|
||||
|
@ -85,9 +94,9 @@ pub(crate) fn cmp_members(
|
|||
if order_by_type {
|
||||
prefix(alias1.name, classes, constants, variables)
|
||||
.cmp(&prefix(alias2.name, classes, constants, variables))
|
||||
.then_with(|| cmp_modules(alias1, alias2, force_to_top))
|
||||
.then_with(|| cmp_modules(alias1, alias2, force_to_top, case_sensitive))
|
||||
} else {
|
||||
cmp_modules(alias1, alias2, force_to_top)
|
||||
cmp_modules(alias1, alias2, force_to_top, case_sensitive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +125,7 @@ pub(crate) fn cmp_import_from(
|
|||
import_from2: &ImportFromData,
|
||||
relative_imports_order: RelativeImportsOrder,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
case_sensitive: bool,
|
||||
) -> Ordering {
|
||||
cmp_levels(
|
||||
import_from1.level,
|
||||
|
@ -133,8 +143,13 @@ pub(crate) fn cmp_import_from(
|
|||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(Some(module1), Some(module2)) => natord::compare_ignore_case(module1, module2)
|
||||
.then_with(|| natord::compare(module1, module2)),
|
||||
(Some(module1), Some(module2)) => {
|
||||
if case_sensitive {
|
||||
natord::compare(module1, module2)
|
||||
} else {
|
||||
natord::compare_ignore_case(module1, module2)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -143,9 +158,14 @@ fn cmp_import_import_from(
|
|||
import: &AliasData,
|
||||
import_from: &ImportFromData,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
case_sensitive: bool,
|
||||
) -> Ordering {
|
||||
cmp_force_to_top(import.name, &import_from.module_name(), force_to_top).then_with(|| {
|
||||
natord::compare_ignore_case(import.name, import_from.module.unwrap_or_default())
|
||||
if case_sensitive {
|
||||
natord::compare(import.name, import_from.module.unwrap_or_default())
|
||||
} else {
|
||||
natord::compare_ignore_case(import.name, import_from.module.unwrap_or_default())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -156,20 +176,24 @@ pub(crate) fn cmp_either_import(
|
|||
b: &EitherImport,
|
||||
relative_imports_order: RelativeImportsOrder,
|
||||
force_to_top: &BTreeSet<String>,
|
||||
case_sensitive: bool,
|
||||
) -> Ordering {
|
||||
match (a, b) {
|
||||
(Import((alias1, _)), Import((alias2, _))) => cmp_modules(alias1, alias2, force_to_top),
|
||||
(Import((alias1, _)), Import((alias2, _))) => {
|
||||
cmp_modules(alias1, alias2, force_to_top, case_sensitive)
|
||||
}
|
||||
(ImportFrom((import_from, ..)), Import((alias, _))) => {
|
||||
cmp_import_import_from(alias, import_from, force_to_top).reverse()
|
||||
cmp_import_import_from(alias, import_from, force_to_top, case_sensitive).reverse()
|
||||
}
|
||||
(Import((alias, _)), ImportFrom((import_from, ..))) => {
|
||||
cmp_import_import_from(alias, import_from, force_to_top)
|
||||
cmp_import_import_from(alias, import_from, force_to_top, case_sensitive)
|
||||
}
|
||||
(ImportFrom((import_from1, ..)), ImportFrom((import_from2, ..))) => cmp_import_from(
|
||||
import_from1,
|
||||
import_from2,
|
||||
relative_imports_order,
|
||||
force_to_top,
|
||||
case_sensitive,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
7
ruff.schema.json
generated
7
ruff.schema.json
generated
|
@ -1135,6 +1135,13 @@
|
|||
"IsortOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"case-sensitive": {
|
||||
"description": "Sort imports taking into account case sensitivity.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"classes": {
|
||||
"description": "An override list of tokens to always recognize as a Class for `order-by-type` regardless of casing.",
|
||||
"type": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue