Skip to content

Commit ab16817

Browse files
committed
feat(playground): support load formatter options and output formatter IR (#14856)
1 parent 8fe7e85 commit ab16817

File tree

3 files changed

+245
-27
lines changed

3 files changed

+245
-27
lines changed

napi/playground/index.d.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,41 @@ export interface OxcDefineOptions {
6868
define: Record<string, string>
6969
}
7070

71+
export interface OxcFormatterOptions {
72+
/** Use tabs instead of spaces (default: false) */
73+
useTabs?: boolean
74+
/** Number of spaces per indentation level (default: 2) */
75+
tabWidth?: number
76+
/** Line ending type: "lf" | "crlf" | "cr" (default: "lf") */
77+
endOfLine?: string
78+
/** Maximum line width (default: 80) */
79+
printWidth?: number
80+
/** Use single quotes instead of double quotes (default: false) */
81+
singleQuote?: boolean
82+
/** Use single quotes in JSX (default: false) */
83+
jsxSingleQuote?: boolean
84+
/** When to add quotes around object properties: "as-needed" | "preserve" (default: "as-needed") */
85+
quoteProps?: string
86+
/** Print trailing commas: "all" | "es5" | "none" (default: "all") */
87+
trailingComma?: string
88+
/** Print semicolons (default: true) */
89+
semi?: boolean
90+
/** Include parentheses around arrow function parameters: "always" | "avoid" (default: "always") */
91+
arrowParens?: string
92+
/** Print spaces between brackets in object literals (default: true) */
93+
bracketSpacing?: boolean
94+
/** Put > of multi-line elements at the end of the last line (default: false) */
95+
bracketSameLine?: boolean
96+
/** Object wrapping style: "preserve" | "collapse" | "always" (default: "preserve") */
97+
objectWrap?: string
98+
/** Put each attribute on its own line (default: false) */
99+
singleAttributePerLine?: boolean
100+
/** Operator position: "start" | "end" (default: "end") */
101+
experimentalOperatorPosition?: string
102+
/** Sort imports configuration */
103+
experimentalSortImports?: OxcSortImportsOptions
104+
}
105+
71106
export interface OxcInjectOptions {
72107
/** Map of variable name to module source or [source, specifier] */
73108
inject: Record<string, string | [string, string]>
@@ -90,6 +125,7 @@ export interface OxcOptions {
90125
run: OxcRunOptions
91126
parser: OxcParserOptions
92127
linter?: OxcLinterOptions
128+
formatter?: OxcFormatterOptions
93129
transformer?: OxcTransformerOptions
94130
isolatedDeclarations?: OxcIsolatedDeclarationsOptions
95131
codegen?: OxcCodegenOptions
@@ -111,7 +147,6 @@ export interface OxcParserOptions {
111147
export interface OxcRunOptions {
112148
lint: boolean
113149
formatter: boolean
114-
formatterIr: boolean
115150
transform: boolean
116151
isolatedDeclarations: boolean
117152
whitespace: boolean
@@ -122,6 +157,19 @@ export interface OxcRunOptions {
122157
cfg: boolean
123158
}
124159

160+
export interface OxcSortImportsOptions {
161+
/** Partition imports by newlines (default: false) */
162+
partitionByNewline?: boolean
163+
/** Partition imports by comments (default: false) */
164+
partitionByComment?: boolean
165+
/** Sort side effects imports (default: false) */
166+
sortSideEffects?: boolean
167+
/** Sort order: "asc" | "desc" (default: "asc") */
168+
order?: string
169+
/** Ignore case when sorting (default: true) */
170+
ignoreCase?: boolean
171+
}
172+
125173
export interface OxcTransformerOptions {
126174
target?: string
127175
useDefineForClassFields: boolean

napi/playground/src/lib.rs

Lines changed: 143 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ use oxc::{
2929
syntax::reference::ReferenceFlags,
3030
transformer::{TransformOptions, Transformer},
3131
};
32-
use oxc_formatter::{FormatOptions, Formatter};
32+
use oxc_formatter::{
33+
ArrowParentheses, AttributePosition, BracketSameLine, BracketSpacing, Expand, FormatOptions,
34+
Formatter, IndentStyle, IndentWidth, LineEnding, LineWidth, OperatorPosition, QuoteProperties,
35+
QuoteStyle, Semicolons, SortImports, SortOrder, TrailingCommas,
36+
};
3337
use oxc_linter::{
3438
ConfigStore, ConfigStoreBuilder, ContextSubHost, ExternalPluginStore, LintOptions, Linter,
3539
ModuleRecord, Oxlintrc,
@@ -41,8 +45,9 @@ use oxc_transformer_plugins::{
4145
};
4246

4347
use crate::options::{
44-
OxcControlFlowOptions, OxcDefineOptions, OxcInjectOptions, OxcIsolatedDeclarationsOptions,
45-
OxcLinterOptions, OxcOptions, OxcParserOptions, OxcRunOptions, OxcTransformerOptions,
48+
OxcControlFlowOptions, OxcDefineOptions, OxcFormatterOptions, OxcInjectOptions,
49+
OxcIsolatedDeclarationsOptions, OxcLinterOptions, OxcOptions, OxcParserOptions, OxcRunOptions,
50+
OxcTransformerOptions,
4651
};
4752

4853
mod options;
@@ -100,6 +105,7 @@ impl Oxc {
100105
run: ref run_options,
101106
parser: ref parser_options,
102107
linter: ref linter_options,
108+
formatter: ref formatter_options,
103109
transformer: ref transform_options,
104110
control_flow: ref control_flow_options,
105111
isolated_declarations: ref isolated_declarations_options,
@@ -109,6 +115,7 @@ impl Oxc {
109115
..
110116
} = options;
111117
let linter_options = linter_options.clone().unwrap_or_default();
118+
let formatter_options = formatter_options.clone().unwrap_or_default();
112119
let transform_options = transform_options.clone().unwrap_or_default();
113120
let control_flow_options = control_flow_options.clone().unwrap_or_default();
114121
let codegen_options = codegen_options.clone().unwrap_or_default();
@@ -142,14 +149,7 @@ impl Oxc {
142149
&allocator,
143150
);
144151

145-
// Phase 4: Run formatter
146-
let parse_options = ParseOptions {
147-
parse_regular_expression: true,
148-
allow_return_outside_function: parser_options.allow_return_outside_function,
149-
preserve_parens: parser_options.preserve_parens,
150-
allow_v8_intrinsics: parser_options.allow_v8_intrinsics,
151-
};
152-
self.run_formatter(run_options, parse_options, &source_text, source_type);
152+
self.run_formatter(run_options, &source_text, source_type, &formatter_options);
153153

154154
let mut scoping = semantic.into_scoping();
155155

@@ -414,30 +414,148 @@ impl Oxc {
414414
}
415415
}
416416

417+
fn convert_formatter_options(options: &OxcFormatterOptions) -> FormatOptions {
418+
let mut format_options = FormatOptions::default();
419+
420+
if let Some(use_tabs) = options.use_tabs {
421+
format_options.indent_style =
422+
if use_tabs { IndentStyle::Tab } else { IndentStyle::Space };
423+
}
424+
425+
if let Some(tab_width) = options.tab_width
426+
&& let Ok(width) = IndentWidth::try_from(tab_width)
427+
{
428+
format_options.indent_width = width;
429+
}
430+
431+
if let Some(ref end_of_line) = options.end_of_line
432+
&& let Ok(ending) = end_of_line.parse::<LineEnding>()
433+
{
434+
format_options.line_ending = ending;
435+
}
436+
437+
if let Some(print_width) = options.print_width
438+
&& let Ok(width) = LineWidth::try_from(print_width)
439+
{
440+
format_options.line_width = width;
441+
}
442+
443+
if let Some(single_quote) = options.single_quote {
444+
format_options.quote_style =
445+
if single_quote { QuoteStyle::Single } else { QuoteStyle::Double };
446+
}
447+
448+
if let Some(jsx_single_quote) = options.jsx_single_quote {
449+
format_options.jsx_quote_style =
450+
if jsx_single_quote { QuoteStyle::Single } else { QuoteStyle::Double };
451+
}
452+
453+
if let Some(ref quote_props) = options.quote_props
454+
&& let Ok(props) = quote_props.parse::<QuoteProperties>()
455+
{
456+
format_options.quote_properties = props;
457+
}
458+
459+
if let Some(ref trailing_comma) = options.trailing_comma
460+
&& let Ok(commas) = trailing_comma.parse::<TrailingCommas>()
461+
{
462+
format_options.trailing_commas = commas;
463+
}
464+
465+
if let Some(semi) = options.semi {
466+
format_options.semicolons =
467+
if semi { Semicolons::Always } else { Semicolons::AsNeeded };
468+
}
469+
470+
if let Some(ref arrow_parens) = options.arrow_parens {
471+
let normalized =
472+
if arrow_parens == "avoid" { "as-needed" } else { arrow_parens.as_str() };
473+
if let Ok(parens) = normalized.parse::<ArrowParentheses>() {
474+
format_options.arrow_parentheses = parens;
475+
}
476+
}
477+
478+
if let Some(bracket_spacing) = options.bracket_spacing {
479+
format_options.bracket_spacing = BracketSpacing::from(bracket_spacing);
480+
}
481+
482+
if let Some(bracket_same_line) = options.bracket_same_line {
483+
format_options.bracket_same_line = BracketSameLine::from(bracket_same_line);
484+
}
485+
486+
if let Some(single_attribute_per_line) = options.single_attribute_per_line {
487+
format_options.attribute_position = if single_attribute_per_line {
488+
AttributePosition::Multiline
489+
} else {
490+
AttributePosition::Auto
491+
};
492+
}
493+
494+
if let Some(ref object_wrap) = options.object_wrap {
495+
let normalized = match object_wrap.as_str() {
496+
"preserve" => "auto",
497+
"collapse" => "never",
498+
_ => object_wrap.as_str(),
499+
};
500+
if let Ok(expand) = normalized.parse::<Expand>() {
501+
format_options.expand = expand;
502+
}
503+
}
504+
505+
if let Some(ref position) = options.experimental_operator_position
506+
&& let Ok(op_position) = position.parse::<OperatorPosition>()
507+
{
508+
format_options.experimental_operator_position = op_position;
509+
}
510+
511+
if let Some(ref sort_imports_config) = options.experimental_sort_imports {
512+
let order = sort_imports_config
513+
.order
514+
.as_ref()
515+
.and_then(|o| o.parse::<SortOrder>().ok())
516+
.unwrap_or_default();
517+
518+
format_options.experimental_sort_imports = Some(SortImports {
519+
partition_by_newline: sort_imports_config.partition_by_newline.unwrap_or(false),
520+
partition_by_comment: sort_imports_config.partition_by_comment.unwrap_or(false),
521+
sort_side_effects: sort_imports_config.sort_side_effects.unwrap_or(false),
522+
order,
523+
ignore_case: sort_imports_config.ignore_case.unwrap_or(true),
524+
});
525+
}
526+
527+
format_options
528+
}
529+
417530
fn run_formatter(
418531
&mut self,
419532
run_options: &OxcRunOptions,
420-
parser_options: ParseOptions,
421533
source_text: &str,
422534
source_type: SourceType,
535+
formatter_options: &OxcFormatterOptions,
423536
) {
424537
let allocator = Allocator::default();
425-
if run_options.formatter || run_options.formatter_ir {
538+
if run_options.formatter {
426539
let ret = Parser::new(&allocator, source_text, source_type)
427-
.with_options(ParseOptions { preserve_parens: false, ..parser_options })
540+
.with_options(ParseOptions {
541+
preserve_parens: false,
542+
allow_return_outside_function: true,
543+
allow_v8_intrinsics: true,
544+
parse_regular_expression: false,
545+
})
428546
.parse();
429547

430-
let formatter = Formatter::new(&allocator, FormatOptions::default());
431-
self.formatter_formatted_text = formatter.build(&ret.program);
432-
433-
// if run_options.formatter_ir.unwrap_or_default() {
434-
// let formatter_doc = formatter.doc(&ret.program).to_string();
435-
// self.formatter_ir_text = {
436-
// let ret =
437-
// Parser::new(&allocator, &formatter_doc, SourceType::default()).parse();
438-
// Formatter::new(&allocator, FormatOptions::default()).build(&ret.program)
439-
// };
440-
// }
548+
let format_options = Self::convert_formatter_options(formatter_options);
549+
let formatter = Formatter::new(&allocator, format_options);
550+
let formatted = formatter.format(&ret.program);
551+
if run_options.formatter {
552+
self.formatter_formatted_text = match formatted.print() {
553+
Ok(printer) => printer.into_code(),
554+
Err(err) => err.to_string(),
555+
};
556+
557+
self.formatter_ir_text = formatted.into_document().to_string();
558+
}
441559
}
442560
}
443561

napi/playground/src/options.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct OxcOptions {
88
pub run: OxcRunOptions,
99
pub parser: OxcParserOptions,
1010
pub linter: Option<OxcLinterOptions>,
11+
pub formatter: Option<OxcFormatterOptions>,
1112
pub transformer: Option<OxcTransformerOptions>,
1213
pub isolated_declarations: Option<OxcIsolatedDeclarationsOptions>,
1314
pub codegen: Option<OxcCodegenOptions>,
@@ -23,7 +24,6 @@ pub struct OxcOptions {
2324
pub struct OxcRunOptions {
2425
pub lint: bool,
2526
pub formatter: bool,
26-
pub formatter_ir: bool,
2727
pub transform: bool,
2828
pub isolated_declarations: bool,
2929
pub whitespace: bool,
@@ -112,3 +112,55 @@ pub struct OxcMangleOptions {
112112
#[napi(object)]
113113
#[derive(Clone, Copy, Default)]
114114
pub struct OxcCompressOptions;
115+
116+
#[napi(object)]
117+
#[derive(Default, Clone)]
118+
pub struct OxcFormatterOptions {
119+
/// Use tabs instead of spaces (default: false)
120+
pub use_tabs: Option<bool>,
121+
/// Number of spaces per indentation level (default: 2)
122+
pub tab_width: Option<u8>,
123+
/// Line ending type: "lf" | "crlf" | "cr" (default: "lf")
124+
pub end_of_line: Option<String>,
125+
/// Maximum line width (default: 80)
126+
pub print_width: Option<u16>,
127+
/// Use single quotes instead of double quotes (default: false)
128+
pub single_quote: Option<bool>,
129+
/// Use single quotes in JSX (default: false)
130+
pub jsx_single_quote: Option<bool>,
131+
/// When to add quotes around object properties: "as-needed" | "preserve" (default: "as-needed")
132+
pub quote_props: Option<String>,
133+
/// Print trailing commas: "all" | "es5" | "none" (default: "all")
134+
pub trailing_comma: Option<String>,
135+
/// Print semicolons (default: true)
136+
pub semi: Option<bool>,
137+
/// Include parentheses around arrow function parameters: "always" | "avoid" (default: "always")
138+
pub arrow_parens: Option<String>,
139+
/// Print spaces between brackets in object literals (default: true)
140+
pub bracket_spacing: Option<bool>,
141+
/// Put > of multi-line elements at the end of the last line (default: false)
142+
pub bracket_same_line: Option<bool>,
143+
/// Object wrapping style: "preserve" | "collapse" | "always" (default: "preserve")
144+
pub object_wrap: Option<String>,
145+
/// Put each attribute on its own line (default: false)
146+
pub single_attribute_per_line: Option<bool>,
147+
/// Operator position: "start" | "end" (default: "end")
148+
pub experimental_operator_position: Option<String>,
149+
/// Sort imports configuration
150+
pub experimental_sort_imports: Option<OxcSortImportsOptions>,
151+
}
152+
153+
#[napi(object)]
154+
#[derive(Default, Clone)]
155+
pub struct OxcSortImportsOptions {
156+
/// Partition imports by newlines (default: false)
157+
pub partition_by_newline: Option<bool>,
158+
/// Partition imports by comments (default: false)
159+
pub partition_by_comment: Option<bool>,
160+
/// Sort side effects imports (default: false)
161+
pub sort_side_effects: Option<bool>,
162+
/// Sort order: "asc" | "desc" (default: "asc")
163+
pub order: Option<String>,
164+
/// Ignore case when sorting (default: true)
165+
pub ignore_case: Option<bool>,
166+
}

0 commit comments

Comments
 (0)