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
4 changes: 2 additions & 2 deletions crates/oxc_formatter/examples/sort_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::{fs, path::Path};

use oxc_allocator::Allocator;
use oxc_formatter::{FormatOptions, Formatter, SortImports, SortOrder, get_parse_options};
use oxc_formatter::{FormatOptions, Formatter, SortImportsOptions, SortOrder, get_parse_options};
use oxc_parser::Parser;
use oxc_span::SourceType;
use pico_args::Arguments;
Expand All @@ -21,7 +21,7 @@ fn main() -> Result<(), String> {
let ignore_case = !args.contains("--no_ignore_case");
let newlines_between = !args.contains("--no_newlines_between");

let sort_imports_options = SortImports {
let sort_imports_options = SortImportsOptions {
order,
partition_by_newline,
partition_by_comment,
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_formatter/src/ir_transform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@

mod sort_imports;

pub use sort_imports::SortImportsTransform;
pub use sort_imports::*;
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ use std::{borrow::Cow, path::Path};
use cow_utils::CowUtils;
use phf::phf_set;

use crate::{
ir_transform::sort_imports::{
group_config::{GroupName, ImportModifier, ImportSelector},
source_line::ImportLineMetadata,
},
options,
use crate::ir_transform::sort_imports::{
group_config::{GroupName, ImportModifier, ImportSelector},
options::SortImportsOptions,
source_line::ImportLineMetadata,
};

/// Compute all metadata derived from import line metadata.
Expand All @@ -17,7 +15,7 @@ use crate::{
pub fn compute_import_metadata<'a>(
metadata: &ImportLineMetadata<'a>,
groups: &[Vec<GroupName>],
options: &options::SortImports,
options: &SortImportsOptions,
) -> (usize, Cow<'a, str>, bool) {
let ImportLineMetadata {
source,
Expand Down Expand Up @@ -347,7 +345,7 @@ enum ImportPathKind {
}

/// Determine the path kind for an import source.
fn to_path_kind(source: &str, options: &options::SortImports) -> ImportPathKind {
fn to_path_kind(source: &str, options: &SortImportsOptions) -> ImportPathKind {
if is_builtin(source) {
return ImportPathKind::Builtin;
}
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_formatter/src/ir_transform/sort_imports/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod compute_metadata;
mod group_config;
pub mod options;
mod partitioned_chunk;
mod sortable_imports;
mod source_line;
Expand All @@ -13,19 +14,18 @@ use crate::{
partitioned_chunk::PartitionedChunk,
source_line::SourceLine,
},
options,
};

/// An IR transform that sorts import statements according to specified options.
/// Heavily inspired by ESLint's `@perfectionist/sort-imports` rule.
/// <https://perfectionist.dev/rules/sort-imports>
pub struct SortImportsTransform {
options: options::SortImports,
options: options::SortImportsOptions,
groups: Vec<Vec<GroupName>>,
}

impl SortImportsTransform {
pub fn new(options: options::SortImports) -> Self {
pub fn new(options: options::SortImportsOptions) -> Self {
// Parse string based groups into our internal representation for performance
let groups = if let Some(groups) = &options.groups {
parse_groups_from_strings(groups)
Expand Down
90 changes: 90 additions & 0 deletions crates/oxc_formatter/src/ir_transform/sort_imports/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::fmt;
use std::str::FromStr;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SortImportsOptions {
/// 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,
/// Whether to insert blank lines between different import groups.
/// - `true`: Insert one blank line between groups (default)
/// - `false`: No blank lines between groups
///
/// NOTE: Cannot be used together with `partition_by_newline: true`.
pub newlines_between: bool,
/// Prefixes for internal imports.
/// If `None`, uses the default internal patterns.
pub internal_pattern: Option<Vec<String>>,
/// Groups configuration for organizing imports.
/// Each inner `Vec` represents a group, and multiple group names in the same `Vec` are treated as one.
/// If `None`, uses the default groups.
pub groups: Option<Vec<Vec<String>>>,
}

impl Default for SortImportsOptions {
fn default() -> Self {
Self {
partition_by_newline: false,
partition_by_comment: false,
sort_side_effects: false,
order: SortOrder::default(),
ignore_case: true,
newlines_between: true,
internal_pattern: None,
groups: None,
}
}
}

#[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<Self, Self::Err> {
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)
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::{
ir_transform::sort_imports::{
compute_metadata::compute_import_metadata,
group_config::GroupName,
sortable_imports::{SortSortableImports, SortableImport},
source_line::SourceLine,
},
options,
use crate::ir_transform::sort_imports::{
compute_metadata::compute_import_metadata,
group_config::GroupName,
options::SortImportsOptions,
sortable_imports::{SortSortableImports, SortableImport},
source_line::SourceLine,
};

#[derive(Debug)]
Expand Down Expand Up @@ -50,7 +48,7 @@ impl<'a> PartitionedChunk<'a> {
pub fn into_sorted_import_units(
self,
groups: &[Vec<GroupName>],
options: &options::SortImports,
options: &SortImportsOptions,
) -> (Vec<SortableImport<'a>>, Vec<SourceLine<'a>>) {
let Self::Imports(lines) = self else {
unreachable!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;

use rustc_hash::{FxHashMap, FxHashSet};

use crate::{ir_transform::sort_imports::source_line::SourceLine, options};
use crate::ir_transform::sort_imports::{options::SortImportsOptions, source_line::SourceLine};

#[derive(Debug)]
pub struct SortableImport<'a> {
Expand All @@ -18,11 +18,11 @@ pub struct SortableImport<'a> {
// ---

pub trait SortSortableImports {
fn sort(&mut self, options: &options::SortImports);
fn sort(&mut self, options: &SortImportsOptions);
}

impl SortSortableImports for Vec<SortableImport<'_>> {
fn sort(&mut self, options: &options::SortImports) {
fn sort(&mut self, options: &SortImportsOptions) {
let imports_len = self.len();

// Perform sorting only if needed
Expand Down Expand Up @@ -90,7 +90,7 @@ impl SortSortableImports for Vec<SortableImport<'_>> {
fn sort_within_group(
indices: &mut [usize],
imports: &[SortableImport],
options: &options::SortImports,
options: &SortImportsOptions,
) {
if indices.len() < 2 {
return;
Expand Down Expand Up @@ -148,7 +148,7 @@ fn sort_within_group(
fn sort_indices_by_source(
indices: &mut [usize],
imports: &[SortableImport],
options: &options::SortImports,
options: &SortImportsOptions,
) {
indices.sort_by(|&a, &b| {
natord::compare(&imports[a].normalized_source, &imports[b].normalized_source)
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use oxc_allocator::Allocator;
use oxc_ast::ast::*;

pub use crate::embedded_formatter::{EmbeddedFormatter, EmbeddedFormatterCallback};
pub use crate::ir_transform::options::*;
pub use crate::options::*;
pub use crate::service::{oxfmtrc::Oxfmtrc, parse_utils::*};
use crate::{
Expand Down
94 changes: 2 additions & 92 deletions crates/oxc_formatter/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
prelude::{if_group_breaks, token},
printer::PrinterOptions,
},
ir_transform::options::SortImportsOptions,
write,
};

Expand Down Expand Up @@ -71,9 +72,8 @@ pub struct FormatOptions {
/// Enable formatting for embedded languages (e.g., CSS, SQL, GraphQL) within template literals. Defaults to "auto".
pub embedded_language_formatting: EmbeddedLanguageFormatting,

// TODO: `FormatOptions`? Split out as `TransformOptions`?
/// Sort import statements. By default disabled.
pub experimental_sort_imports: Option<SortImports>,
pub experimental_sort_imports: Option<SortImportsOptions>,
}

impl FormatOptions {
Expand Down Expand Up @@ -978,93 +978,3 @@ impl fmt::Display for EmbeddedLanguageFormatting {
f.write_str(s)
}
}

// ---

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SortImports {
/// 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,
/// Whether to insert blank lines between different import groups.
/// - `true`: Insert one blank line between groups (default)
/// - `false`: No blank lines between groups
///
/// NOTE: Cannot be used together with `partition_by_newline: true`.
pub newlines_between: bool,
/// Prefixes for internal imports.
/// If `None`, uses the default internal patterns.
pub internal_pattern: Option<Vec<String>>,
/// Groups configuration for organizing imports.
/// Each inner `Vec` represents a group, and multiple group names in the same `Vec` are treated as one.
/// If `None`, uses the default groups.
pub groups: Option<Vec<Vec<String>>>,
}

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,
newlines_between: true,
internal_pattern: None,
groups: None,
}
}
}

#[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<Self, Self::Err> {
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)
}
}
5 changes: 3 additions & 2 deletions crates/oxc_formatter/src/service/oxfmtrc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use serde::{Deserialize, Deserializer, Serialize};
use crate::{
ArrowParentheses, AttributePosition, BracketSameLine, BracketSpacing,
EmbeddedLanguageFormatting, Expand, FormatOptions, IndentStyle, IndentWidth, LineEnding,
LineWidth, QuoteProperties, QuoteStyle, Semicolons, SortImports, SortOrder, TrailingCommas,
LineWidth, QuoteProperties, QuoteStyle, Semicolons, SortImportsOptions, SortOrder,
TrailingCommas,
};

/// Configuration options for the formatter.
Expand Down Expand Up @@ -370,7 +371,7 @@ impl Oxfmtrc {
return Err("Invalid `sortImports` configuration: `partitionByNewline: true` and `newlinesBetween: true` cannot be used together".to_string());
}

options.experimental_sort_imports = Some(SortImports {
options.experimental_sort_imports = Some(SortImportsOptions {
partition_by_newline: sort_imports_config.partition_by_newline,
partition_by_comment: sort_imports_config.partition_by_comment,
sort_side_effects: sort_imports_config.sort_side_effects,
Expand Down
Loading
Loading