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
66 changes: 66 additions & 0 deletions crates/oxc_formatter/examples/sort_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#![expect(clippy::print_stdout)]

use std::{fs, path::Path};

use oxc_allocator::Allocator;
use oxc_formatter::{FormatOptions, Formatter, SortImports};
use oxc_parser::{ParseOptions, Parser};
use oxc_span::SourceType;
use pico_args::Arguments;

/// Format a JavaScript or TypeScript file
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());

// Read source file
let path = Path::new(&name);
let source_text = fs::read_to_string(path).map_err(|_| format!("Missing '{name}'"))?;
let source_type = SourceType::from_path(path).unwrap();
let allocator = Allocator::new();

// Parse the source code
let ret = Parser::new(&allocator, &source_text, source_type)
.with_options(ParseOptions {
parse_regular_expression: false,
// Enable all syntax features
allow_v8_intrinsics: true,
allow_return_outside_function: true,
// `oxc_formatter` expects this to be false
preserve_parens: false,
})
.parse();

// Report any parsing errors
for error in ret.errors {
let error = error.with_source_code(source_text.clone());
println!("{error:?}");
println!("Parsed with Errors.");
}

// Format the parsed code
let options = FormatOptions {
experimental_sort_imports: Some(SortImports {
partition_by_newline: true,
partition_by_comment: false,
sort_side_effects: true,
}),
..Default::default()
};

let formatter = Formatter::new(&allocator, options);
if show_ir {
let doc = formatter.doc(&ret.program);
println!("[");
for el in doc.iter() {
println!(" {el:?},");
}
println!("]");
} else {
let code = formatter.build(&ret.program);
println!("{code}");
}

Ok(())
}
6 changes: 6 additions & 0 deletions crates/oxc_formatter/src/formatter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ impl<'a> Formatted<'a> {
}
}

impl<'a> Formatted<'a> {
pub fn apply_transform(&mut self, transform: impl FnOnce(&Document<'a>) -> Document<'a>) {
self.document = transform(&self.document);
}
}

impl Formatted<'_> {
pub fn print(&self) -> PrintResult<Printed> {
let print_options = self.context.options().as_print_options();
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_formatter/src/ir_transform/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod sort_imports;

pub use sort_imports::SortImportsTransform;
32 changes: 32 additions & 0 deletions crates/oxc_formatter/src/ir_transform/sort_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::{formatter::format_element::document::Document, options::SortImports};

pub struct SortImportsTransform {
options: SortImports,
}

impl SortImportsTransform {
pub fn new(options: SortImports) -> Self {
Self { options }
}

pub fn transform<'a>(&self, document: &Document<'a>) -> Document<'a> {
let mut new_elements = Vec::with_capacity(document.len());

// TODO: THESE ARE DUMMY IMPLEMENTATIONS!
let mut temp = None;
for (idx, element) in document.iter().enumerate() {
if idx == 0 {
temp = Some(element);
continue;
}

new_elements.push(element.clone());
}

if let Some(temp) = temp {
new_elements.insert(new_elements.len() - 1, temp.clone());
}

Document::from(new_elements)
}
}
18 changes: 16 additions & 2 deletions crates/oxc_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod generated {
mod format;
}
mod formatter;
mod ir_transform;
mod options;
mod parentheses;
mod service;
Expand All @@ -38,6 +39,7 @@ pub use crate::service::source_type::get_supported_source_type;
use crate::{
formatter::{FormatContext, Formatted, format_element::document::Document},
generated::ast_nodes::{AstNode, AstNodes},
ir_transform::SortImportsTransform,
};

use self::formatter::prelude::tag::Label;
Expand Down Expand Up @@ -71,13 +73,25 @@ impl<'a> Formatter<'a> {

let source_text = program.source_text;
self.source_text = source_text;

let experimental_sort_imports = self.options.experimental_sort_imports;

let context = FormatContext::new(program, self.allocator, self.options);
formatter::format(
let mut formatted = formatter::format(
program,
context,
formatter::Arguments::new(&[formatter::Argument::new(&program_node)]),
)
.unwrap()
.unwrap();

// Basic formatting and `document.propagate_expand()` are already done here.
// Now apply additional transforms if enabled.
if let Some(sort_imports_options) = experimental_sort_imports {
let sort_imports = SortImportsTransform::new(sort_imports_options);
formatted.apply_transform(|doc| sort_imports.transform(doc));
}

formatted
}
}

Expand Down
18 changes: 17 additions & 1 deletion crates/oxc_formatter/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ pub struct FormatOptions {
/// - `"start"`: Places the operator at the beginning of the next line.
/// - `"end"`: Places the operator at the end of the current line (default).
pub experimental_operator_position: OperatorPosition,

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

impl FormatOptions {
Expand All @@ -82,6 +86,7 @@ impl FormatOptions {
attribute_position: AttributePosition::default(),
expand: Expand::default(),
experimental_operator_position: OperatorPosition::default(),
experimental_sort_imports: None,
}
}

Expand All @@ -106,7 +111,8 @@ impl fmt::Display for FormatOptions {
writeln!(f, "Bracket same line: {}", self.bracket_same_line.value())?;
writeln!(f, "Attribute Position: {}", self.attribute_position)?;
writeln!(f, "Expand lists: {}", self.expand)?;
writeln!(f, "Experimental operator position: {}", self.experimental_operator_position)
writeln!(f, "Experimental operator position: {}", self.experimental_operator_position)?;
writeln!(f, "Experimental sort imports: {:?}", self.experimental_sort_imports)
}
}

Expand Down Expand Up @@ -908,3 +914,13 @@ impl fmt::Display for OperatorPosition {
f.write_str(s)
}
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct SortImports {
/// Partition imports by newlines.
pub partition_by_newline: bool,
/// Partition imports by comments.
pub partition_by_comment: bool,
/// Sort side effects imports.
pub sort_side_effects: bool,
}
Loading