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