Skip to content

Commit 4e6bee7

Browse files
committed
feat(formatter): Experimental sort-imports base
1 parent f3b61d8 commit 4e6bee7

File tree

6 files changed

+140
-3
lines changed

6 files changed

+140
-3
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![expect(clippy::print_stdout)]
2+
3+
use std::{fs, path::Path};
4+
5+
use oxc_allocator::Allocator;
6+
use oxc_formatter::{FormatOptions, Formatter, SortImports};
7+
use oxc_parser::{ParseOptions, Parser};
8+
use oxc_span::SourceType;
9+
use pico_args::Arguments;
10+
11+
/// Format a JavaScript or TypeScript file
12+
fn main() -> Result<(), String> {
13+
let mut args = Arguments::from_env();
14+
let show_ir = args.contains("--ir");
15+
let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string());
16+
17+
// Read source file
18+
let path = Path::new(&name);
19+
let source_text = fs::read_to_string(path).map_err(|_| format!("Missing '{name}'"))?;
20+
let source_type = SourceType::from_path(path).unwrap();
21+
let allocator = Allocator::new();
22+
23+
// Parse the source code
24+
let ret = Parser::new(&allocator, &source_text, source_type)
25+
.with_options(ParseOptions {
26+
parse_regular_expression: false,
27+
// Enable all syntax features
28+
allow_v8_intrinsics: true,
29+
allow_return_outside_function: true,
30+
// `oxc_formatter` expects this to be false
31+
preserve_parens: false,
32+
})
33+
.parse();
34+
35+
// Report any parsing errors
36+
for error in ret.errors {
37+
let error = error.with_source_code(source_text.clone());
38+
println!("{error:?}");
39+
println!("Parsed with Errors.");
40+
}
41+
42+
// Format the parsed code
43+
let options = FormatOptions {
44+
experimental_sort_imports: Some(SortImports {
45+
partition_by_newline: true,
46+
partition_by_comment: false,
47+
sort_side_effects: true,
48+
}),
49+
..Default::default()
50+
};
51+
52+
let formatter = Formatter::new(&allocator, options);
53+
if show_ir {
54+
let doc = formatter.doc(&ret.program);
55+
println!("[");
56+
for el in doc.iter() {
57+
println!(" {el:?},");
58+
}
59+
println!("]");
60+
} else {
61+
let code = formatter.build(&ret.program);
62+
println!("{code}");
63+
}
64+
65+
Ok(())
66+
}

crates/oxc_formatter/src/formatter/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ impl<'a> Formatted<'a> {
105105
}
106106
}
107107

108+
impl<'a> Formatted<'a> {
109+
pub fn apply_transform(&mut self, transform: impl FnOnce(&Document<'a>) -> Document<'a>) {
110+
self.document = transform(&self.document);
111+
}
112+
}
113+
108114
impl Formatted<'_> {
109115
pub fn print(&self) -> PrintResult<Printed> {
110116
let print_options = self.context.options().as_print_options();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod sort_imports;
2+
3+
pub use sort_imports::SortImportsTransform;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::{formatter::format_element::document::Document, options::SortImports};
2+
3+
pub struct SortImportsTransform {
4+
options: SortImports,
5+
}
6+
7+
impl SortImportsTransform {
8+
pub fn new(options: SortImports) -> Self {
9+
Self { options }
10+
}
11+
12+
pub fn transform<'a>(&self, document: &Document<'a>) -> Document<'a> {
13+
let mut new_elements = Vec::with_capacity(document.len());
14+
15+
// TODO: THESE ARE DUMMY IMPLEMENTATIONS!
16+
let mut temp = None;
17+
for (idx, element) in document.iter().enumerate() {
18+
if idx == 0 {
19+
temp = Some(element);
20+
continue;
21+
}
22+
23+
new_elements.push(element.clone());
24+
}
25+
26+
if let Some(temp) = temp {
27+
new_elements.insert(new_elements.len() - 1, temp.clone());
28+
}
29+
30+
Document::from(new_elements)
31+
}
32+
}

crates/oxc_formatter/src/lib.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod generated {
1414
mod format;
1515
}
1616
mod formatter;
17+
mod ir_transform;
1718
mod options;
1819
mod parentheses;
1920
mod service;
@@ -38,6 +39,7 @@ pub use crate::service::source_type::get_supported_source_type;
3839
use crate::{
3940
formatter::{FormatContext, Formatted, format_element::document::Document},
4041
generated::ast_nodes::{AstNode, AstNodes},
42+
ir_transform::SortImportsTransform,
4143
};
4244

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

7274
let source_text = program.source_text;
7375
self.source_text = source_text;
76+
77+
let experimental_sort_imports = self.options.experimental_sort_imports;
78+
7479
let context = FormatContext::new(program, self.allocator, self.options);
75-
formatter::format(
80+
let mut formatted = formatter::format(
7681
program,
7782
context,
7883
formatter::Arguments::new(&[formatter::Argument::new(&program_node)]),
7984
)
80-
.unwrap()
85+
.unwrap();
86+
87+
// Basic formatting and `document.propagate_expand()` are already done here.
88+
// Now apply additional transforms if enabled.
89+
if let Some(sort_imports_options) = experimental_sort_imports {
90+
let sort_imports = SortImportsTransform::new(sort_imports_options);
91+
formatted.apply_transform(|doc| sort_imports.transform(doc));
92+
}
93+
94+
formatted
8195
}
8296
}
8397

crates/oxc_formatter/src/options.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ pub struct FormatOptions {
6262
/// - `"start"`: Places the operator at the beginning of the next line.
6363
/// - `"end"`: Places the operator at the end of the current line (default).
6464
pub experimental_operator_position: OperatorPosition,
65+
66+
// TODO: `FormatOptions`? Split out as `TransformOptions`?
67+
/// Sort import statements. By default disabled.
68+
pub experimental_sort_imports: Option<SortImports>,
6569
}
6670

6771
impl FormatOptions {
@@ -82,6 +86,7 @@ impl FormatOptions {
8286
attribute_position: AttributePosition::default(),
8387
expand: Expand::default(),
8488
experimental_operator_position: OperatorPosition::default(),
89+
experimental_sort_imports: None,
8590
}
8691
}
8792

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

@@ -908,3 +914,13 @@ impl fmt::Display for OperatorPosition {
908914
f.write_str(s)
909915
}
910916
}
917+
918+
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
919+
pub struct SortImports {
920+
/// Partition imports by newlines.
921+
pub partition_by_newline: bool,
922+
/// Partition imports by comments.
923+
pub partition_by_comment: bool,
924+
/// Sort side effects imports.
925+
pub sort_side_effects: bool,
926+
}

0 commit comments

Comments
 (0)