Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions crates/oxc_formatter/examples/sort_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ fn main() -> Result<(), String> {
let partition_by_comment = args.contains("--partition_by_comment");
let sort_side_effects = args.contains("--sort_side_effects");
let order = args.opt_value_from_str("--order").unwrap_or(None).unwrap_or(SortOrder::Asc);
let ignore_case = !args.contains("--no_ignore_case");

let sort_imports_options =
SortImports { order, partition_by_newline, partition_by_comment, sort_side_effects };
let sort_imports_options = SortImports {
order,
partition_by_newline,
partition_by_comment,
sort_side_effects,
ignore_case,
};

// Read source file
let path = Path::new(&name);
Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_formatter/src/ir_transform/sort_imports.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::ops::Range;

use cow_utils::CowUtils;

use crate::{
JsLabels,
formatter::format_element::{
Expand Down Expand Up @@ -434,7 +436,7 @@ impl IntoIterator for ImportUnits {

impl ImportUnits {
// TODO: Sort based on `options.groups`, `options.type`, etc...
// TODO: Consider `options.ignore_case`, `special_characters`, removing `?raw`, etc...
// TODO: Consider `special_characters`, removing `?raw`, etc...
fn sort_imports(&mut self, elements: &[FormatElement], options: options::SortImports) {
let imports_len = self.0.len();

Expand All @@ -458,7 +460,15 @@ impl ImportUnits {

// Sort indices by comparing their corresponding import sources
sortable_indices.sort_by(|&a, &b| {
let ord = self.0[a].get_source(elements).cmp(self.0[b].get_source(elements));
let source_a = self.0[a].get_source(elements);
let source_b = self.0[b].get_source(elements);

let ord = if options.ignore_case {
source_a.cow_to_lowercase().cmp(&source_b.cow_to_lowercase())
} else {
source_a.cmp(source_b)
};

if options.order.is_desc() { ord.reverse() } else { ord }
});

Expand Down
25 changes: 22 additions & 3 deletions crates/oxc_formatter/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,16 +917,35 @@ impl fmt::Display for OperatorPosition {

// ---

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SortImports {
/// Sort order (asc or desc).
pub order: SortOrder,
/// Partition imports by newlines.
/// Default is `false`.
pub partition_by_newline: bool,
/// Partition imports by comments.
/// Default is `false`.
pub partition_by_comment: bool,
/// Sort side effects imports.
/// Default is `false`.
pub sort_side_effects: bool,
/// Sort order (asc or desc).
/// Default is ascending (asc).
pub order: SortOrder,
/// Ignore case when sorting.
/// Default is `true`.
pub ignore_case: bool,
}

impl Default for SortImports {
fn default() -> Self {
Self {
partition_by_newline: false,
partition_by_comment: false,
sort_side_effects: false,
order: SortOrder::default(),
ignore_case: true,
}
}
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
Expand Down
132 changes: 132 additions & 0 deletions crates/oxc_formatter/tests/ir_transform/sort_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,24 @@ import { log } from "./log";
import { log10 } from "./log10";
import { log1p } from "./log1p";
import { log2 } from "./log2";
"#,
);
assert_format(
r#"
import { log } from "./log";
import { log10 } from "./log10";
import { log1p } from "./log1p";
import { log2 } from "./log2";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports::default()),
..Default::default()
},
r#"
import { log } from "./log";
import { log10 } from "./log10";
import { log1p } from "./log1p";
import { log2 } from "./log2";
"#,
);
}
Expand Down Expand Up @@ -818,3 +836,117 @@ import "c";
"#,
);
}

// ---

#[test]
fn should_sort_with_ignore_case_option() {
// Case-insensitive (ignore_case: true by default)
assert_format(
r#"
import { A } from "a";
import { b } from "B";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports::default()),
..Default::default()
},
r#"
import { A } from "a";
import { b } from "B";
"#,
);
// "a" and "A" are treated as the same, maintaining original order
assert_format(
r#"
import x from "A";
import y from "a";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports::default()),
..Default::default()
},
r#"
import x from "A";
import y from "a";
"#,
);
// Mixed case sorting with ignore_case: true
assert_format(
r#"
import { z } from "Z";
import { b } from "B";
import { a } from "a";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports {
ignore_case: true,
..Default::default()
}),
..Default::default()
},
r#"
import { a } from "a";
import { b } from "B";
import { z } from "Z";
"#,
);
// Case-sensitive, lowercase comes after uppercase in ASCII
assert_format(
r#"
import { a } from "a";
import { B } from "B";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports {
ignore_case: false,
..Default::default()
}),
..Default::default()
},
r#"
import { B } from "B";
import { a } from "a";
"#,
);
// Capital A vs lowercase a
assert_format(
r#"
import x from "a";
import y from "A";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports {
ignore_case: false,
..Default::default()
}),
..Default::default()
},
r#"
import y from "A";
import x from "a";
"#,
);
// Multiple imports with mixed case
assert_format(
r#"
import { z } from "z";
import { B } from "B";
import { a } from "a";
import { Z } from "Z";
"#,
&FormatOptions {
experimental_sort_imports: Some(SortImports {
ignore_case: false,
..Default::default()
}),
..Default::default()
},
r#"
import { B } from "B";
import { Z } from "Z";
import { a } from "a";
import { z } from "z";
"#,
);
}
Loading