Skip to content

Commit

Permalink
Implement isort's reverse_relative setting (#1826)
Browse files Browse the repository at this point in the history
This PR implements `reverse-relative`, from isort, but renames it to
`relative-imports-order` with the respected value `closest-to-furthest`
and `furthest-to-closest`, and the latter being the default.

Closes #1813.
  • Loading branch information
charliermarsh committed Jan 12, 2023
1 parent 39aae28 commit 3110d34
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 34 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,30 @@ order-by-type = true

---

#### [`relative-imports-order`](#relative-imports-order)

Whether to place "closer" imports (fewer `.` characters, most local)
before "further" imports (more `.` characters, least local), or vice
versa.

The default ("furthest-to-closest") is equivalent to isort's
`reverse-relative` default (`reverse-relative = false`); setting
this to "closest-to-furthest" is equivalent to isort's `reverse-relative
= true`.

**Default value**: `furthest-to-closest`

**Type**: `RelatveImportsOrder`

**Example usage**:

```toml
[tool.ruff.isort]
relative-imports-order = "closest-to-furthest"
```

---

#### [`required-imports`](#required-imports)

Add the specified import line to all files.
Expand Down
3 changes: 3 additions & 0 deletions resources/test/fixtures/isort/relative_imports_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ... import a
from .. import b
from . import c
29 changes: 29 additions & 0 deletions ruff.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,17 @@
"null"
]
},
"relative-imports-order": {
"description": "Whether to place \"closer\" imports (fewer `.` characters, most local) before \"further\" imports (more `.` characters, least local), or vice versa.\n\nThe default (\"furthest-to-closest\") is equivalent to isort's `reverse-relative` default (`reverse-relative = false`); setting this to \"closest-to-furthest\" is equivalent to isort's `reverse-relative = true`.",
"anyOf": [
{
"$ref": "#/definitions/RelatveImportsOrder"
},
{
"type": "null"
}
]
},
"required-imports": {
"description": "Add the specified import line to all files.",
"type": [
Expand Down Expand Up @@ -1007,6 +1018,24 @@
}
]
},
"RelatveImportsOrder": {
"oneOf": [
{
"description": "Place \"closer\" imports (fewer `.` characters, most local) before \"further\" imports (more `.` characters, least local).",
"type": "string",
"enum": [
"closest-to-further"
]
},
{
"description": "Place \"further\" imports (more `.` characters, least local) imports before \"closer\" imports (fewer `.` characters, most local).",
"type": "string",
"enum": [
"furthest-to-closest"
]
}
]
},
"RuleCodePrefix": {
"type": "string",
"enum": [
Expand Down
58 changes: 44 additions & 14 deletions src/isort/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use rustpython_ast::{Stmt, StmtKind};
use crate::isort::categorize::{categorize, ImportType};
use crate::isort::comments::Comment;
use crate::isort::helpers::trailing_comma;
use crate::isort::settings::RelatveImportsOrder;
use crate::isort::sorting::{cmp_either_import, cmp_import_from, cmp_members, cmp_modules};
use crate::isort::track::{Block, Trailer};
use crate::isort::types::EitherImport::{Import, ImportFrom};
Expand Down Expand Up @@ -382,7 +383,11 @@ fn categorize_imports<'a>(
block_by_type
}

fn order_imports(block: ImportBlock, order_by_type: bool) -> OrderedImportBlock {
fn order_imports(
block: ImportBlock,
order_by_type: bool,
relative_imports_order: RelatveImportsOrder,
) -> OrderedImportBlock {
let mut ordered = OrderedImportBlock::default();

// Sort `StmtKind::Import`.
Expand Down Expand Up @@ -468,16 +473,16 @@ fn order_imports(block: ImportBlock, order_by_type: bool) -> OrderedImportBlock
})
.sorted_by(
|(import_from1, _, _, aliases1), (import_from2, _, _, aliases2)| {
cmp_import_from(import_from1, import_from2).then_with(|| {
match (aliases1.first(), aliases2.first()) {
cmp_import_from(import_from1, import_from2, relative_imports_order).then_with(
|| match (aliases1.first(), aliases2.first()) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some((alias1, _)), Some((alias2, _))) => {
cmp_members(alias1, alias2, order_by_type)
}
}
})
},
)
},
),
);
Expand Down Expand Up @@ -540,16 +545,17 @@ pub fn format_imports(
stylist: &Stylist,
src: &[PathBuf],
package: Option<&Path>,
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
combine_as_imports: bool,
force_wrap_aliases: bool,
split_on_trailing_comma: bool,
extra_standard_library: &BTreeSet<String>,
force_single_line: bool,
single_line_exclusions: &BTreeSet<String>,
order_by_type: bool,
force_sort_within_sections: bool,
force_wrap_aliases: bool,
known_first_party: &BTreeSet<String>,
known_third_party: &BTreeSet<String>,
order_by_type: bool,
relative_imports_order: RelatveImportsOrder,
single_line_exclusions: &BTreeSet<String>,
split_on_trailing_comma: bool,
) -> String {
let trailer = &block.trailer;
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
Expand All @@ -572,7 +578,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 imports = order_imports(import_block, order_by_type);
let mut imports = order_imports(import_block, order_by_type, relative_imports_order);

if force_single_line {
imports = force_single_line_imports(imports, single_line_exclusions);
Expand All @@ -586,7 +592,9 @@ pub fn format_imports(
.chain(imports.import_from.into_iter().map(ImportFrom))
.collect::<Vec<EitherImport>>();
if force_sort_within_sections {
imports.sort_by(cmp_either_import);
imports.sort_by(|import1, import2| {
cmp_either_import(import1, import2, relative_imports_order)
});
};
imports
};
Expand Down Expand Up @@ -647,6 +655,7 @@ mod tests {
use test_case::test_case;

use crate::isort;
use crate::isort::settings::RelatveImportsOrder;
use crate::linter::test_path;
use crate::registry::RuleCode;
use crate::settings::Settings;
Expand Down Expand Up @@ -678,6 +687,7 @@ mod tests {
#[test_case(Path::new("preserve_import_star.py"))]
#[test_case(Path::new("preserve_indentation.py"))]
#[test_case(Path::new("reorder_within_section.py"))]
#[test_case(Path::new("relative_imports_order.py"))]
#[test_case(Path::new("separate_first_party_imports.py"))]
#[test_case(Path::new("separate_future_imports.py"))]
#[test_case(Path::new("separate_local_folder_imports.py"))]
Expand Down Expand Up @@ -923,4 +933,24 @@ mod tests {
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}

#[test_case(Path::new("relative_imports_order.py"))]
fn closest_to_furthest(path: &Path) -> Result<()> {
let snapshot = format!("closest_to_furthest_{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("./resources/test/fixtures/isort")
.join(path)
.as_path(),
&Settings {
isort: isort::settings::Settings {
relative_imports_order: RelatveImportsOrder::ClosestToFurther,
..isort::settings::Settings::default()
},
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(RuleCode::I001)
},
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
}
15 changes: 8 additions & 7 deletions src/isort/rules/organize_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,17 @@ pub fn organize_imports(
stylist,
&settings.src,
package,
&settings.isort.known_first_party,
&settings.isort.known_third_party,
&settings.isort.extra_standard_library,
settings.isort.combine_as_imports,
settings.isort.force_wrap_aliases,
settings.isort.split_on_trailing_comma,
&settings.isort.extra_standard_library,
settings.isort.force_single_line,
&settings.isort.single_line_exclusions,
settings.isort.order_by_type,
settings.isort.force_sort_within_sections,
settings.isort.force_wrap_aliases,
&settings.isort.known_first_party,
&settings.isort.known_third_party,
settings.isort.order_by_type,
settings.isort.relative_imports_order,
&settings.isort.single_line_exclusions,
settings.isort.split_on_trailing_comma,
);

// Expand the span the entire range, including leading and trailing space.
Expand Down
37 changes: 37 additions & 0 deletions src/isort/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum RelatveImportsOrder {
/// Place "closer" imports (fewer `.` characters, most local) before
/// "further" imports (more `.` characters, least local).
ClosestToFurther,
/// Place "further" imports (more `.` characters, least local) imports
/// before "closer" imports (fewer `.` characters, most local).
FurthestToClosest,
}

impl Default for RelatveImportsOrder {
fn default() -> Self {
Self::FurthestToClosest
}
}

#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
Expand Down Expand Up @@ -129,6 +146,22 @@ pub struct Options {
/// A list of modules to consider standard-library, in addition to those
/// known to Ruff in advance.
pub extra_standard_library: Option<Vec<String>>,
#[option(
default = r#"furthest-to-closest"#,
value_type = "RelatveImportsOrder",
example = r#"
relative-imports-order = "closest-to-furthest"
"#
)]
/// Whether to place "closer" imports (fewer `.` characters, most local)
/// before "further" imports (more `.` characters, least local), or vice
/// versa.
///
/// The default ("furthest-to-closest") is equivalent to isort's
/// `reverse-relative` default (`reverse-relative = false`); setting
/// this to "closest-to-furthest" is equivalent to isort's `reverse-relative
/// = true`.
pub relative_imports_order: Option<RelatveImportsOrder>,
#[option(
default = r#"[]"#,
value_type = "Vec<String>",
Expand All @@ -152,6 +185,7 @@ pub struct Settings {
pub known_first_party: BTreeSet<String>,
pub known_third_party: BTreeSet<String>,
pub order_by_type: bool,
pub relative_imports_order: RelatveImportsOrder,
pub single_line_exclusions: BTreeSet<String>,
pub split_on_trailing_comma: bool,
}
Expand All @@ -168,6 +202,7 @@ impl Default for Settings {
known_first_party: BTreeSet::new(),
known_third_party: BTreeSet::new(),
order_by_type: true,
relative_imports_order: RelatveImportsOrder::default(),
single_line_exclusions: BTreeSet::new(),
split_on_trailing_comma: true,
}
Expand All @@ -188,6 +223,7 @@ impl From<Options> for Settings {
known_first_party: BTreeSet::from_iter(options.known_first_party.unwrap_or_default()),
known_third_party: BTreeSet::from_iter(options.known_third_party.unwrap_or_default()),
order_by_type: options.order_by_type.unwrap_or(true),
relative_imports_order: options.relative_imports_order.unwrap_or_default(),
single_line_exclusions: BTreeSet::from_iter(
options.single_line_exclusions.unwrap_or_default(),
),
Expand All @@ -208,6 +244,7 @@ impl From<Settings> for Options {
known_first_party: Some(settings.known_first_party.into_iter().collect()),
known_third_party: Some(settings.known_third_party.into_iter().collect()),
order_by_type: Some(settings.order_by_type),
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),
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: src/isort/mod.rs
expression: diagnostics
---
- kind:
UnsortedImports: ~
location:
row: 1
column: 0
end_location:
row: 4
column: 0
fix:
content: "from . import c\nfrom .. import b\nfrom ... import a\n"
location:
row: 1
column: 0
end_location:
row: 4
column: 0
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: src/isort/mod.rs
expression: diagnostics
---
[]

Loading

0 comments on commit 3110d34

Please sign in to comment.