1use std::{
2 env,
3 path::{Path, PathBuf},
4};
5
6use anyhow::{Context, Error};
7use base64::prelude::{Engine, BASE64_STANDARD};
8use once_cell::sync::Lazy;
9use rustc_hash::FxHashMap;
10#[allow(unused)]
11use serde::{Deserialize, Serialize};
12use swc_atoms::Atom;
13use swc_common::{
14 comments::{Comment, CommentKind, Comments, SingleThreadedComments},
15 errors::Handler,
16 source_map::SourceMapGenConfig,
17 sync::Lrc,
18 BytePos, FileName, SourceFile, SourceMap,
19};
20use swc_config::config_types::BoolOr;
21pub use swc_config::IsModule;
22use swc_ecma_ast::{EsVersion, Ident, IdentName, Program};
23use swc_ecma_codegen::{text_writer::WriteJs, Emitter, Node};
24use swc_ecma_minifier::js::JsMinifyCommentOption;
25use swc_ecma_parser::{parse_file_as_module, parse_file_as_program, parse_file_as_script, Syntax};
26use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
27use swc_timer::timer;
28
29#[cfg(feature = "node")]
30#[napi_derive::napi(object)]
31#[derive(Debug, Serialize)]
32pub struct TransformOutput {
33 pub code: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub map: Option<String>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub output: Option<String>,
39
40 pub diagnostics: std::vec::Vec<String>,
41}
42
43#[cfg(not(feature = "node"))]
44#[derive(Debug, Serialize)]
45pub struct TransformOutput {
46 pub code: String,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub map: Option<String>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub output: Option<String>,
53
54 pub diagnostics: std::vec::Vec<String>,
55}
56
57pub fn parse_js(
61 _cm: Lrc<SourceMap>,
62 fm: Lrc<SourceFile>,
63 handler: &Handler,
64 target: EsVersion,
65 syntax: Syntax,
66 is_module: IsModule,
67 comments: Option<&dyn Comments>,
68) -> Result<Program, Error> {
69 let mut res = (|| {
70 let mut error = false;
71
72 let mut errors = std::vec::Vec::new();
73 let program_result = match is_module {
74 IsModule::Bool(true) => {
75 parse_file_as_module(&fm, syntax, target, comments, &mut errors)
76 .map(Program::Module)
77 }
78 IsModule::Bool(false) => {
79 parse_file_as_script(&fm, syntax, target, comments, &mut errors)
80 .map(Program::Script)
81 }
82 IsModule::Unknown => parse_file_as_program(&fm, syntax, target, comments, &mut errors),
83 };
84
85 for e in errors {
86 e.into_diagnostic(handler).emit();
87 error = true;
88 }
89
90 let program = program_result.map_err(|e| {
91 e.into_diagnostic(handler).emit();
92 Error::msg("Syntax Error")
93 })?;
94
95 if error {
96 return Err(anyhow::anyhow!("Syntax Error"));
97 }
98
99 Ok(program)
100 })();
101
102 if env::var("SWC_DEBUG").unwrap_or_default() == "1" {
103 res = res.with_context(|| format!("Parser config: {:?}", syntax));
104 }
105
106 res
107}
108
109pub struct PrintArgs<'a> {
110 pub source_root: Option<&'a str>,
111 pub source_file_name: Option<&'a str>,
112 pub output_path: Option<PathBuf>,
113 pub inline_sources_content: bool,
114 pub source_map: SourceMapsConfig,
115 pub source_map_names: &'a FxHashMap<BytePos, Atom>,
116 pub orig: Option<&'a sourcemap::SourceMap>,
117 pub comments: Option<&'a dyn Comments>,
118 pub emit_source_map_columns: bool,
119 pub preamble: &'a str,
120 pub codegen_config: swc_ecma_codegen::Config,
121 pub output: Option<FxHashMap<String, serde_json::Value>>,
122}
123
124impl Default for PrintArgs<'_> {
125 fn default() -> Self {
126 static DUMMY_NAMES: Lazy<FxHashMap<BytePos, Atom>> = Lazy::new(Default::default);
127
128 PrintArgs {
129 source_root: None,
130 source_file_name: None,
131 output_path: None,
132 inline_sources_content: false,
133 source_map: Default::default(),
134 source_map_names: &DUMMY_NAMES,
135 orig: None,
136 comments: None,
137 emit_source_map_columns: false,
138 preamble: "",
139 codegen_config: Default::default(),
140 output: None,
141 }
142 }
143}
144
145#[allow(clippy::too_many_arguments)]
155pub fn print<T>(
156 cm: Lrc<SourceMap>,
157 node: &T,
158 PrintArgs {
159 source_root,
160 source_file_name,
161 output_path,
162 inline_sources_content,
163 source_map,
164 source_map_names,
165 orig,
166 comments,
167 emit_source_map_columns,
168 preamble,
169 codegen_config,
170 output,
171 }: PrintArgs,
172) -> Result<TransformOutput, Error>
173where
174 T: Node + VisitWith<IdentCollector>,
175{
176 let _timer = timer!("Compiler::print");
177
178 let mut src_map_buf = Vec::new();
179
180 let src = {
181 let mut buf = std::vec::Vec::new();
182 {
183 let mut w = swc_ecma_codegen::text_writer::JsWriter::new(
184 cm.clone(),
185 "\n",
186 &mut buf,
187 if source_map.enabled() {
188 Some(&mut src_map_buf)
189 } else {
190 None
191 },
192 );
193 w.preamble(preamble).unwrap();
194 let mut wr = Box::new(w) as Box<dyn WriteJs>;
195
196 if codegen_config.minify {
197 wr = Box::new(swc_ecma_codegen::text_writer::omit_trailing_semi(wr));
198 }
199
200 let mut emitter = Emitter {
201 cfg: codegen_config,
202 comments,
203 cm: cm.clone(),
204 wr,
205 };
206
207 node.emit_with(&mut emitter)
208 .context("failed to emit module")?;
209 }
210 String::from_utf8(buf).expect("invalid utf8 character detected")
212 };
213
214 if cfg!(debug_assertions)
215 && !src_map_buf.is_empty()
216 && src_map_buf.iter().all(|(bp, _)| bp.is_dummy())
217 && src.lines().count() >= 3
218 && option_env!("SWC_DEBUG") == Some("1")
219 {
220 panic!("The module contains only dummy spans\n{}", src);
221 }
222
223 let mut map = if source_map.enabled() {
224 Some(cm.build_source_map_with_config(
225 &src_map_buf,
226 orig,
227 SwcSourceMapConfig {
228 source_file_name,
229 output_path: output_path.as_deref(),
230 names: source_map_names,
231 inline_sources_content,
232 emit_columns: emit_source_map_columns,
233 },
234 ))
235 } else {
236 None
237 };
238
239 if let Some(map) = &mut map {
240 if source_root.is_some() {
241 map.set_source_root(source_root)
242 }
243 }
244
245 let (code, map) = match source_map {
246 SourceMapsConfig::Bool(v) => {
247 if v {
248 let mut buf = std::vec::Vec::new();
249
250 map.unwrap()
251 .to_writer(&mut buf)
252 .context("failed to write source map")?;
253 let map = String::from_utf8(buf).context("source map is not utf-8")?;
254 (src, Some(map))
255 } else {
256 (src, None)
257 }
258 }
259 SourceMapsConfig::Str(_) => {
260 let mut src = src;
261 let mut buf = std::vec::Vec::new();
262
263 map.unwrap()
264 .to_writer(&mut buf)
265 .context("failed to write source map file")?;
266 let map = String::from_utf8(buf).context("source map is not utf-8")?;
267
268 src.push_str("\n//# sourceMappingURL=data:application/json;base64,");
269 BASE64_STANDARD.encode_string(map.as_bytes(), &mut src);
270 (src, None)
271 }
272 };
273
274 Ok(TransformOutput {
275 code,
276 map,
277 output: output
278 .map(|v| serde_json::to_string(&v).context("failed to serilaize output"))
279 .transpose()?,
280 diagnostics: Default::default(),
281 })
282}
283
284struct SwcSourceMapConfig<'a> {
285 source_file_name: Option<&'a str>,
286 output_path: Option<&'a Path>,
288
289 names: &'a FxHashMap<BytePos, Atom>,
290
291 inline_sources_content: bool,
292
293 emit_columns: bool,
294}
295
296impl SourceMapGenConfig for SwcSourceMapConfig<'_> {
297 fn file_name_to_source(&self, f: &FileName) -> String {
298 if let Some(file_name) = self.source_file_name {
299 return file_name.to_string();
300 }
301
302 let base_path = match self.output_path {
303 Some(v) => v,
304 None => return f.to_string(),
305 };
306 let target = match f {
307 FileName::Real(v) => v,
308 _ => return f.to_string(),
309 };
310
311 let rel = pathdiff::diff_paths(target, base_path);
312 match rel {
313 Some(v) => {
314 let s = v.to_string_lossy().to_string();
315 if cfg!(target_os = "windows") {
316 s.replace('\\', "/")
317 } else {
318 s
319 }
320 }
321 None => f.to_string(),
322 }
323 }
324
325 fn name_for_bytepos(&self, pos: BytePos) -> Option<&str> {
326 self.names.get(&pos).map(|v| &**v)
327 }
328
329 fn inline_sources_content(&self, _: &FileName) -> bool {
330 self.inline_sources_content
331 }
332
333 fn emit_columns(&self, _f: &FileName) -> bool {
334 self.emit_columns
335 }
336
337 fn skip(&self, f: &FileName) -> bool {
338 match f {
339 FileName::Internal(..) => true,
340 FileName::Custom(s) => s.starts_with('<'),
341 _ => false,
342 }
343 }
344}
345
346pub fn minify_file_comments(
347 comments: &SingleThreadedComments,
348 preserve_comments: BoolOr<JsMinifyCommentOption>,
349 preserve_annotations: bool,
350) {
351 match preserve_comments {
352 BoolOr::Bool(true) | BoolOr::Data(JsMinifyCommentOption::PreserveAllComments) => {}
353
354 BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments) => {
355 let preserve_excl = |_: &BytePos, vc: &mut std::vec::Vec<Comment>| -> bool {
356 vc.retain(|c: &Comment| {
360 c.text.contains("@lic")
361 || c.text.contains("@preserve")
362 || c.text.contains("@copyright")
363 || c.text.contains("@cc_on")
364 || (preserve_annotations
365 && (c.text.contains("__PURE__")
366 || c.text.contains("__INLINE__")
367 || c.text.contains("__NOINLINE__")
368 || c.text.contains("@vite-ignore")))
369 || (c.kind == CommentKind::Block && c.text.starts_with('!'))
370 });
371 !vc.is_empty()
372 };
373 let (mut l, mut t) = comments.borrow_all_mut();
374
375 l.retain(preserve_excl);
376 t.retain(preserve_excl);
377 }
378
379 BoolOr::Bool(false) => {
380 let (mut l, mut t) = comments.borrow_all_mut();
381 l.clear();
382 t.clear();
383 }
384 }
385}
386
387#[derive(Clone, Serialize, Deserialize, Debug)]
389#[serde(untagged)]
390pub enum SourceMapsConfig {
391 Bool(bool),
392 Str(String),
393}
394
395impl SourceMapsConfig {
396 pub fn enabled(&self) -> bool {
397 match *self {
398 SourceMapsConfig::Bool(b) => b,
399 SourceMapsConfig::Str(ref s) => {
400 assert_eq!(s, "inline", "Source map must be true, false or inline");
401 true
402 }
403 }
404 }
405}
406
407impl Default for SourceMapsConfig {
408 fn default() -> Self {
409 SourceMapsConfig::Bool(true)
410 }
411}
412
413pub struct IdentCollector {
414 pub names: FxHashMap<BytePos, Atom>,
415}
416
417impl Visit for IdentCollector {
418 noop_visit_type!();
419
420 fn visit_ident(&mut self, ident: &Ident) {
421 self.names.insert(ident.span.lo, ident.sym.clone());
422 }
423
424 fn visit_ident_name(&mut self, ident: &IdentName) {
425 self.names.insert(ident.span.lo, ident.sym.clone());
426 }
427}