[`isort`] Add `classes` Config Option (#1849)

ref https://github.com/charliermarsh/ruff/issues/1819
This commit is contained in:
Maksudul Haque 2023-01-13 23:13:01 +06:00 committed by GitHub
parent 66b1d09362
commit 84ef7a0171
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 145 additions and 11 deletions

View File

@ -2877,6 +2877,24 @@ ignore-variadic-names = true
### `isort`
#### [`classes`](#classes)
An override list of tokens to always recognize as a Class for
`order-by-type` regardless of casing.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
classes = ["SVC"]
```
---
#### [`combine-as-imports`](#combine-as-imports)
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)

View File

@ -0,0 +1,4 @@
from sklearn.svm import func, SVC, CONST, Klass
from subprocess import N_CLASS, PIPE, Popen, STDOUT
from module import CLASS, Class, CONSTANT, function, BASIC, Apple
from torch.nn import SELU, AClass, A_CONSTANT

View File

@ -755,6 +755,16 @@
"IsortOptions": {
"type": "object",
"properties": {
"classes": {
"description": "An override list of tokens to always recognize as a Class for `order-by-type` regardless of casing.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"combine-as-imports": {
"description": "Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports) option.",
"type": [

View File

@ -383,11 +383,12 @@ fn categorize_imports<'a>(
block_by_type
}
fn order_imports(
block: ImportBlock,
fn order_imports<'a>(
block: ImportBlock<'a>,
order_by_type: bool,
relative_imports_order: RelatveImportsOrder,
) -> OrderedImportBlock {
classes: &'a BTreeSet<String>,
) -> OrderedImportBlock<'a> {
let mut ordered = OrderedImportBlock::default();
// Sort `StmtKind::Import`.
@ -466,7 +467,7 @@ fn order_imports(
aliases
.into_iter()
.sorted_by(|(alias1, _), (alias2, _)| {
cmp_members(alias1, alias2, order_by_type)
cmp_members(alias1, alias2, order_by_type, classes)
})
.collect::<Vec<(AliasData, CommentSet)>>(),
)
@ -479,7 +480,7 @@ fn order_imports(
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some((alias1, _)), Some((alias2, _))) => {
cmp_members(alias1, alias2, order_by_type)
cmp_members(alias1, alias2, order_by_type, classes)
}
},
)
@ -556,6 +557,7 @@ pub fn format_imports(
relative_imports_order: RelatveImportsOrder,
single_line_exclusions: &BTreeSet<String>,
split_on_trailing_comma: bool,
classes: &BTreeSet<String>,
) -> String {
let trailer = &block.trailer;
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
@ -578,7 +580,8 @@ 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 imports = order_imports(import_block, order_by_type, relative_imports_order);
let mut imports =
order_imports(import_block, order_by_type, relative_imports_order, classes);
if force_single_line {
imports = force_single_line_imports(imports, single_line_exclusions);
@ -698,6 +701,7 @@ mod tests {
#[test_case(Path::new("split.py"))]
#[test_case(Path::new("trailing_suffix.py"))]
#[test_case(Path::new("type_comments.py"))]
#[test_case(Path::new("order_by_type_with_custom_classes.py"))]
fn default(path: &Path) -> Result<()> {
let snapshot = format!("{}", path.to_string_lossy());
let diagnostics = test_path(
@ -818,6 +822,36 @@ mod tests {
Ok(())
}
#[test_case(Path::new("order_by_type_with_custom_classes.py"))]
fn order_by_type_with_custom_classes(path: &Path) -> Result<()> {
let snapshot = format!(
"order_by_type_with_custom_classes_{}",
path.to_string_lossy()
);
let mut diagnostics = test_path(
Path::new("./resources/test/fixtures/isort")
.join(path)
.as_path(),
&Settings {
isort: isort::settings::Settings {
order_by_type: true,
classes: BTreeSet::from([
"SVC".to_string(),
"SELU".to_string(),
"N_CLASS".to_string(),
"CLASS".to_string(),
]),
..isort::settings::Settings::default()
},
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(RuleCode::I001)
},
)?;
diagnostics.sort_by_key(|diagnostic| diagnostic.location);
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
#[test_case(Path::new("force_sort_within_sections.py"))]
fn force_sort_within_sections(path: &Path) -> Result<()> {
let snapshot = format!("force_sort_within_sections_{}", path.to_string_lossy());

View File

@ -84,6 +84,7 @@ pub fn organize_imports(
settings.isort.relative_imports_order,
&settings.isort.single_line_exclusions,
settings.isort.split_on_trailing_comma,
&settings.isort.classes,
);
// Expand the span the entire range, including leading and trailing space.

View File

@ -171,6 +171,16 @@ pub struct Options {
)]
/// Add the specified import line to all files.
pub required_imports: Option<Vec<String>>,
#[option(
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
classes = ["SVC"]
"#
)]
/// An override list of tokens to always recognize as a Class for
/// `order-by-type` regardless of casing.
pub classes: Option<Vec<String>>,
}
#[derive(Debug, Hash)]
@ -188,6 +198,7 @@ pub struct Settings {
pub relative_imports_order: RelatveImportsOrder,
pub single_line_exclusions: BTreeSet<String>,
pub split_on_trailing_comma: bool,
pub classes: BTreeSet<String>,
}
impl Default for Settings {
@ -205,6 +216,7 @@ impl Default for Settings {
relative_imports_order: RelatveImportsOrder::default(),
single_line_exclusions: BTreeSet::new(),
split_on_trailing_comma: true,
classes: BTreeSet::new(),
}
}
}
@ -228,6 +240,7 @@ impl From<Options> for Settings {
options.single_line_exclusions.unwrap_or_default(),
),
split_on_trailing_comma: options.split_on_trailing_comma.unwrap_or(true),
classes: BTreeSet::from_iter(options.classes.unwrap_or_default()),
}
}
}
@ -247,6 +260,7 @@ impl From<Settings> for Options {
relative_imports_order: Some(settings.relative_imports_order),
single_line_exclusions: Some(settings.single_line_exclusions.into_iter().collect()),
split_on_trailing_comma: Some(settings.split_on_trailing_comma),
classes: Some(settings.classes.into_iter().collect()),
}
}
}

View File

@ -0,0 +1,22 @@
---
source: src/isort/mod.rs
expression: diagnostics
---
- kind:
UnsortedImports: ~
location:
row: 1
column: 0
end_location:
row: 5
column: 0
fix:
content: "from subprocess import N_CLASS, PIPE, STDOUT, Popen\n\nfrom module import BASIC, CLASS, CONSTANT, Apple, Class, function\nfrom sklearn.svm import CONST, SVC, Klass, func\nfrom torch.nn import A_CONSTANT, SELU, AClass\n"
location:
row: 1
column: 0
end_location:
row: 5
column: 0
parent: ~

View File

@ -0,0 +1,22 @@
---
source: src/isort/mod.rs
expression: diagnostics
---
- kind:
UnsortedImports: ~
location:
row: 1
column: 0
end_location:
row: 5
column: 0
fix:
content: "from subprocess import PIPE, STDOUT, N_CLASS, Popen\n\nfrom module import BASIC, CONSTANT, Apple, CLASS, Class, function\nfrom sklearn.svm import CONST, Klass, SVC, func\nfrom torch.nn import A_CONSTANT, AClass, SELU\n"
location:
row: 1
column: 0
end_location:
row: 5
column: 0
parent: ~

View File

@ -1,5 +1,6 @@
/// See: <https://github.com/PyCQA/isort/blob/12cc5fbd67eebf92eb2213b03c07b138ae1fb448/isort/sorting.py#L13>
use std::cmp::Ordering;
use std::collections::BTreeSet;
use crate::isort::settings::RelatveImportsOrder;
use crate::isort::types::EitherImport::{Import, ImportFrom};
@ -13,8 +14,11 @@ pub enum Prefix {
Variables,
}
fn prefix(name: &str) -> Prefix {
if name.len() > 1 && string::is_upper(name) {
fn prefix(name: &str, classes: &BTreeSet<String>) -> Prefix {
if classes.contains(name) {
// Ex) `CLASS`
Prefix::Classes
} else if name.len() > 1 && string::is_upper(name) {
// Ex) `CONSTANT`
Prefix::Constants
} else if name.chars().next().map_or(false, char::is_uppercase) {
@ -39,10 +43,15 @@ 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, order_by_type: bool) -> Ordering {
pub fn cmp_members(
alias1: &AliasData,
alias2: &AliasData,
order_by_type: bool,
classes: &BTreeSet<String>,
) -> Ordering {
if order_by_type {
prefix(alias1.name)
.cmp(&prefix(alias2.name))
prefix(alias1.name, classes)
.cmp(&prefix(alias2.name, classes))
.then_with(|| cmp_modules(alias1, alias2))
} else {
cmp_modules(alias1, alias2)