1use std::sync::Arc;
7
8#[cfg(feature = "clap")]
9use clap::Parser;
10use oxc::{
11 allocator::Allocator,
12 codegen::{Codegen, CodegenOptions},
13 diagnostics::Severity,
14 mangler::{MangleOptions, MangleOptionsKeepNames},
15 semantic::SemanticBuilder,
16 span::SourceType,
17};
18
19#[cfg_attr(feature = "clap", derive(Debug, Parser))]
21#[expect(clippy::struct_excessive_bools)]
22pub struct Args {
23 #[cfg_attr(feature = "clap", arg(short = 'o', long = "output"))]
26 #[cfg(feature = "clap")]
27 pub output: Option<String>,
28 #[cfg_attr(
31 feature = "clap",
32 arg(short = 'S', long = "import", default_value = "@surplus/s")
33 )]
34 pub import_sjs: String,
35 #[cfg_attr(feature = "clap", arg(short = 'W'))]
37 pub warnings_as_errors: bool,
38 #[cfg_attr(feature = "clap", arg(short = 'M', long = "no-minify"))]
40 pub no_minify: bool,
41 #[cfg_attr(feature = "clap", arg(short = 'm', long = "map"))]
43 pub generate_sourcemaps: bool,
44 pub entry_point: Option<String>,
47 #[cfg_attr(feature = "clap", arg(short = 'T', long = "typescript"))]
49 pub typescript: bool,
50}
51
52pub struct Compilation {
54 pub code: String,
56 pub warnings: Vec<String>,
58 pub errors: Vec<String>,
60}
61
62pub fn run(source: String, args: &Args) -> Result<Compilation, Box<dyn std::error::Error>> {
70 let mut result = Compilation {
71 code: String::new(),
72 warnings: Vec::new(),
73 errors: Vec::new(),
74 };
75
76 let source = Arc::new(source);
77 let mut errors = 0;
78
79 let allocator = Allocator::default();
80 let parse_result = oxc::parser::Parser::new(
81 &allocator,
82 &source,
83 if args.typescript {
84 SourceType::tsx()
85 } else {
86 SourceType::jsx()
87 },
88 )
89 .parse();
90
91 if parse_result.panicked || !parse_result.errors.is_empty() {
92 if parse_result.errors.is_empty() {
93 return Err("parser panicked, but no errors were reported".into());
94 }
95
96 for mut error in parse_result.errors {
97 if args.warnings_as_errors {
98 error = error.with_severity(Severity::Error);
99 }
100
101 if error.severity == Severity::Error {
102 errors += 1;
103 }
104
105 result
106 .errors
107 .push(format!("{:?}", error.with_source_code(Arc::clone(&source))));
108 }
109
110 if errors > 0 {
111 return Ok(result);
112 }
113 }
114
115 let mut program = parse_result.program;
116
117 let semantic = SemanticBuilder::new()
118 .with_check_syntax_error(true)
119 .build(&program);
120
121 if !semantic.errors.is_empty() {
122 errors = 0;
123 for mut error in semantic.errors {
124 if args.warnings_as_errors {
125 error = error.with_severity(Severity::Error);
126 }
127
128 if error.severity == Severity::Error {
129 errors += 1;
130 }
131
132 result
133 .errors
134 .push(format!("{:?}", error.with_source_code(Arc::clone(&source))));
135 }
136
137 if errors > 0 {
138 return Ok(result);
139 }
140 }
141
142 let scoping = semantic.semantic.into_scoping();
143 let surplus_result = surplus::transform(&allocator, &mut program, scoping, &args.import_sjs);
144
145 if !surplus_result.errors.is_empty() {
146 errors = 0;
147 for mut error in surplus_result.errors {
148 if args.warnings_as_errors {
149 error = error.with_severity(Severity::Error);
150 }
151
152 if error.severity == Severity::Error {
153 errors += 1;
154 }
155
156 result
157 .errors
158 .push(format!("{:?}", error.with_source_code(Arc::clone(&source))));
159 }
160
161 if errors > 0 {
162 return Ok(result);
163 }
164 }
165
166 let codegen_options = CodegenOptions {
167 minify: !args.no_minify,
168 comments: args.no_minify,
169 source_map_path: if args.generate_sourcemaps {
170 if let Some(ref entry) = args.entry_point {
171 Some(entry.into())
172 } else {
173 Some("surplus.js.map".into())
174 }
175 } else {
176 None
177 },
178 ..CodegenOptions::default()
179 };
180
181 let scoping = if args.no_minify {
182 surplus_result.scoping
183 } else {
184 let semantic = SemanticBuilder::new()
185 .with_check_syntax_error(false)
186 .with_scope_tree_child_ids(true)
187 .build(&program);
188
189 debug_assert!(semantic.errors.is_empty());
190
191 let options = MangleOptions {
192 keep_names: MangleOptionsKeepNames::all_false(),
193 top_level: true,
194 ..MangleOptions::default()
195 };
196
197 oxc::mangler::Mangler::new()
198 .with_options(options)
199 .build_with_semantic(semantic.semantic, &program)
200 };
201
202 let generated = Codegen::new()
203 .with_options(codegen_options)
204 .with_scoping(Some(scoping))
205 .build(&program);
206
207 let sourcemap_string = if args.generate_sourcemaps {
208 if let Some(ref sourcemap) = generated.map {
209 Some(sourcemap.to_data_url())
210 } else {
211 result
212 .warnings
213 .push("sourcemap generation requested, but no sourcemap was generated".into());
214 None
215 }
216 } else {
217 None
218 };
219
220 result.code = generated.code;
221 if let Some(ref sm) = sourcemap_string {
222 result.code.push_str("\n//# sourceMappingURL=");
223 result.code.push_str(sm);
224 }
225
226 Ok(result)
227}