diff --git a/crates/oxc_formatter/examples/sort_imports.rs b/crates/oxc_formatter/examples/sort_imports.rs index 94cb14a0fe84d..c4c99f4ccfe27 100644 --- a/crates/oxc_formatter/examples/sort_imports.rs +++ b/crates/oxc_formatter/examples/sort_imports.rs @@ -3,7 +3,7 @@ use std::{fs, path::Path}; use oxc_allocator::Allocator; -use oxc_formatter::{FormatOptions, Formatter, SortImports}; +use oxc_formatter::{FormatOptions, Formatter, SortImports, SortOrder}; use oxc_parser::{ParseOptions, Parser}; use oxc_span::SourceType; use pico_args::Arguments; @@ -12,10 +12,15 @@ use pico_args::Arguments; fn main() -> Result<(), String> { let mut args = Arguments::from_env(); let show_ir = args.contains("--ir"); + let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string()); + let partition_by_newline = args.contains("--partition_by_newline"); let partition_by_comment = args.contains("--partition_by_comment"); let sort_side_effects = args.contains("--sort_side_effects"); - let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string()); + let order = args.opt_value_from_str("--order").unwrap_or(None).unwrap_or(SortOrder::Asc); + + let sort_imports_options = + SortImports { order, partition_by_newline, partition_by_comment, sort_side_effects }; // Read source file let path = Path::new(&name); @@ -44,11 +49,7 @@ fn main() -> Result<(), String> { // Format the parsed code let options = FormatOptions { - experimental_sort_imports: Some(SortImports { - partition_by_newline, - partition_by_comment, - sort_side_effects, - }), + experimental_sort_imports: Some(sort_imports_options), ..Default::default() }; @@ -66,10 +67,7 @@ fn main() -> Result<(), String> { } println!("======================="); - println!( - "Formatted with {:#?}", - SortImports { partition_by_newline, partition_by_comment, sort_side_effects } - ); + println!("Formatted with {sort_imports_options:#?}",); Ok(()) } diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports.rs b/crates/oxc_formatter/src/ir_transform/sort_imports.rs index 1c330a6c7011a..d5e5d43755666 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports.rs @@ -171,7 +171,10 @@ impl SortImportsTransform { if 1 < import_units.len() { // TODO: Sort based on `options.groups`, `options.type`, etc... // TODO: Consider `options.ignore_case`, `special_characters`, removing `?raw`, etc... - import_units.sort_by_key(|unit| unit.get_source(prev_elements)); + import_units.sort_by(|a, b| { + let ord = a.get_source(prev_elements).cmp(b.get_source(prev_elements)); + if self.options.order.is_desc() { ord.reverse() } else { ord } + }); } let preserve_empty_line = self.options.partition_by_newline; diff --git a/crates/oxc_formatter/src/options.rs b/crates/oxc_formatter/src/options.rs index ed77c28a05571..a45b87638a2cd 100644 --- a/crates/oxc_formatter/src/options.rs +++ b/crates/oxc_formatter/src/options.rs @@ -915,8 +915,12 @@ impl fmt::Display for OperatorPosition { } } +// --- + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub struct SortImports { + /// Sort order (asc or desc). + pub order: SortOrder, /// Partition imports by newlines. pub partition_by_newline: bool, /// Partition imports by comments. @@ -924,3 +928,44 @@ pub struct SortImports { /// Sort side effects imports. pub sort_side_effects: bool, } + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub enum SortOrder { + /// Sort in ascending order (A-Z). + #[default] + Asc, + /// Sort in descending order (Z-A). + Desc, +} + +impl SortOrder { + pub const fn is_asc(self) -> bool { + matches!(self, Self::Asc) + } + + pub const fn is_desc(self) -> bool { + matches!(self, Self::Desc) + } +} + +impl FromStr for SortOrder { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "asc" => Ok(Self::Asc), + "desc" => Ok(Self::Desc), + _ => Err("Value not supported for SortOrder. Supported values are 'asc' and 'desc'."), + } + } +} + +impl fmt::Display for SortOrder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + SortOrder::Asc => "ASC", + SortOrder::Desc => "DESC", + }; + f.write_str(s) + } +} diff --git a/crates/oxc_formatter/tests/ir_transform/sort_imports.rs b/crates/oxc_formatter/tests/ir_transform/sort_imports.rs index 73ec4e751400c..f3d8a17d175cd 100644 --- a/crates/oxc_formatter/tests/ir_transform/sort_imports.rs +++ b/crates/oxc_formatter/tests/ir_transform/sort_imports.rs @@ -1,5 +1,5 @@ use super::assert_format; -use oxc_formatter::{FormatOptions, QuoteStyle, Semicolons, SortImports}; +use oxc_formatter::{FormatOptions, QuoteStyle, Semicolons, SortImports, SortOrder}; #[test] fn should_not_sort_by_default() { @@ -657,3 +657,53 @@ import B from "b"; "#, ); } + +// --- + +#[test] +fn should_sort_by_order() { + // Z-A + 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 { + order: SortOrder::Desc, + ..Default::default() + }), + ..Default::default() + }, + r#" +import { log2 } from "./log2"; +import { log1p } from "./log1p"; +import { log10 } from "./log10"; +import { log } from "./log"; +"#, + ); + // A-Z - default + 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 { + order: SortOrder::Asc, + ..Default::default() + }), + ..Default::default() + }, + r#" +import { log } from "./log"; +import { log10 } from "./log10"; +import { log1p } from "./log1p"; +import { log2 } from "./log2"; +"#, + ); +}