1use std::path::PathBuf;
2
3use arcstr::ArcStr;
4use oxc::{
5 allocator::Allocator,
6 ast::{
7 ast::{Program, Statement},
8 builder::AstBuilder,
9 },
10 codegen::{Codegen, CodegenOptions, CodegenReturn, CommentOptions, LegalComment},
11 minifier::{ManglePropertyCache, Minifier, MinifierOptions},
12 parser::{ParseOptions, Parser},
13 span::{SPAN, SourceType},
14};
15use oxc_sourcemap::SourceMap;
16use rolldown_error::{BuildDiagnostic, BuildResult, EventKind, Severity};
17
18use crate::ecma_ast::{
19 EcmaAst,
20 program_cell::{ProgramCell, ProgramCellDependent, ProgramCellOwner},
21};
22pub struct EcmaCompiler;
23
24impl EcmaCompiler {
25 pub fn parse(id: &str, source: impl Into<ArcStr>, ty: SourceType) -> BuildResult<EcmaAst> {
26 let source: ArcStr = source.into();
27 let allocator = oxc::allocator::Allocator::default();
28 let inner =
29 ProgramCell::try_new(ProgramCellOwner { source: source.clone(), allocator }, |owner| {
30 let parser = Parser::new(&owner.allocator, &owner.source, ty).with_options(ParseOptions {
31 allow_return_outside_function: true,
32 preserve_parens: false,
33 ..ParseOptions::default()
34 });
35 let ret = parser.parse();
36 if ret.fatal_error || !ret.diagnostics.is_empty() {
37 Err(BuildDiagnostic::from_oxc_diagnostics(
38 ret.diagnostics,
39 &source.clone(),
40 id,
41 Severity::Error,
42 EventKind::ParseError,
43 ))
44 } else {
45 Ok(ProgramCellDependent { program: ret.program })
46 }
47 })?;
48 Ok(EcmaAst { program: inner, source_type: ty })
49 }
50
51 pub fn parse_expr_as_program(
52 id: &str,
53 source: impl Into<ArcStr>,
54 ty: SourceType,
55 ) -> BuildResult<EcmaAst> {
56 let source: ArcStr = source.into();
57 let allocator = oxc::allocator::Allocator::default();
58 let inner =
59 ProgramCell::try_new(ProgramCellOwner { source: source.clone(), allocator }, |owner| {
60 let builder = AstBuilder::new(&owner.allocator);
61 let parser = Parser::new(&owner.allocator, &owner.source, ty)
62 .with_options(ParseOptions { preserve_parens: false, ..ParseOptions::default() });
63 let ret = parser.parse_expression();
64 match ret {
65 Ok(expr) => {
66 let program = Program::new(
67 SPAN,
68 SourceType::default().with_module(true),
69 owner.source.as_str(),
70 [],
71 None,
72 [],
73 [Statement::new_expression_statement(SPAN, expr, &builder)],
74 &builder,
75 );
76 Ok(ProgramCellDependent { program })
77 }
78 Err(errors) => Err(BuildDiagnostic::from_oxc_diagnostics(
79 errors,
80 &source.clone(),
81 id,
82 Severity::Error,
83 EventKind::ParseError,
84 )),
85 }
86 })?;
87 Ok(EcmaAst { program: inner, source_type: ty })
88 }
89
90 pub fn print_with(ast: &EcmaAst, options: PrintOptions) -> CodegenReturn<'_> {
91 let legal = if options.comments.legal { LegalComment::Inline } else { LegalComment::None };
92 Codegen::new()
93 .with_options(CodegenOptions {
94 comments: CommentOptions {
95 normal: false,
96 legal,
97 jsdoc: options.comments.jsdoc,
98 annotation: options.comments.annotation,
99 },
100 initial_indent: options.initial_indent,
101 ascii_only: options.ascii_only,
102 source_map_path: options.sourcemap.then(|| PathBuf::from(options.filename)),
103 ..CodegenOptions::default()
104 })
105 .build(ast.program())
106 }
107
108 #[expect(clippy::too_many_arguments)]
113 pub fn dce_or_minify<'a>(
114 allocator: &'a Allocator,
115 source_text: &'a str,
116 source_type: SourceType,
117 enable_sourcemap: bool,
118 filename: &str,
119 compress: bool,
120 minify_options: MinifierOptions,
121 codegen_options: CodegenOptions,
122 ) -> BuildResult<(String, Option<SourceMap<'a>>, Option<ManglePropertyCache>)> {
123 let ret = Parser::new(allocator, source_text, source_type)
124 .with_options(ParseOptions { preserve_parens: false, ..ParseOptions::default() })
125 .parse();
126 if ret.fatal_error || !ret.diagnostics.is_empty() {
127 return Err(
128 BuildDiagnostic::from_oxc_diagnostics(
129 ret.diagnostics,
130 &ArcStr::from(source_text),
131 filename,
132 Severity::Error,
133 EventKind::ParseError,
134 )
135 .into(),
136 );
137 }
138 let mut program = ret.program;
139 let minifier = Minifier::new(minify_options);
140 let ret = if compress {
141 minifier.minify(allocator, &mut program)
142 } else {
143 minifier.dce(allocator, &mut program)
144 };
145 let property_mangle_cache = ret.property_mangle_cache;
146 let ret = Codegen::new()
147 .with_options(CodegenOptions {
148 source_map_path: enable_sourcemap.then(|| PathBuf::from(filename)),
149 ..codegen_options
150 })
151 .with_scoping(ret.scoping)
152 .with_private_member_mappings(ret.class_private_mappings)
153 .build(&program);
154 Ok((ret.code, ret.map, property_mangle_cache))
155 }
156}
157
158#[test]
159fn basic_test() {
160 let ast = EcmaCompiler::parse("", "const a = 1;".to_string(), SourceType::default()).unwrap();
161 let code = EcmaCompiler::print_with(&ast, PrintOptions::default()).code;
162 assert_eq!(code, "const a = 1;\n");
163}
164#[derive(Debug, Default)]
165
166pub struct PrintOptions {
167 pub comments: PrintCommentsOptions,
168 pub filename: String,
169 pub sourcemap: bool,
170 pub initial_indent: u32,
171 pub ascii_only: bool,
172}
173
174#[derive(Debug, Default, Clone, Copy)]
175pub struct PrintCommentsOptions {
176 pub legal: bool,
177 pub annotation: bool,
178 pub jsdoc: bool,
179}