swc/
lib.rs

1//! The main crate of the swc project.
2//!
3//!
4//!
5//! # Customizing
6//!
7//!
8//! This is documentation for building custom build tools on top of swc.
9//!
10//! ## Dependency version management
11//!
12//! `swc` has [swc_css](https://docs.rs/swc_css), which re-exports required modules.
13//!
14//! ## Testing
15//!
16//! See [testing] and [swc_ecma_transforms_testing](https://docs.rs/swc_ecma_transforms_testing).
17//!
18//! ## Custom javascript transforms
19//!
20//!
21//!
22//! ### What is [Atom](swc_atoms::Atom)?
23//!
24//! It's basically an interned string. See [swc_atoms].
25//!
26//! ### Choosing between [Atom](swc_atoms::Atom) vs String
27//!
28//! You should  prefer [Atom](swc_atoms::Atom) over [String] if it's going
29//! to be stored in an AST node.
30//!
31//! See [swc_atoms] for detailed description.
32//!
33//! ### Fold vs VisitMut vs Visit
34//!
35//! See [swc_visit] for detailed description.
36//!
37//!
38//!  - [Fold](swc_ecma_visit::Fold)
39//!  - [VisitMut](swc_ecma_visit::VisitMut)
40//!  - [Visit](swc_ecma_visit::Visit)
41//!
42//!
43//! ### Variable management (Scoping)
44//!
45//! See [swc_ecma_transforms_base::resolver::resolver_with_mark].
46//!
47//! #### How identifiers work
48//!
49//! See the doc on [swc_ecma_ast::Ident] or on
50//! [swc_ecma_transforms_base::resolver::resolver_with_mark].
51//!
52//! #### Comparing two identifiers
53//!
54//! See [swc_ecma_utils::Id]. You can use [swc_ecma_utils::IdentLike::to_id] to
55//! extract important parts of an [swc_ecma_ast::Ident].
56//!
57//! #### Creating a unique identifier
58//!
59//! See [swc_ecma_utils::private_ident].
60//!
61//! #### Prepending statements
62//!
63//! If you want to prepend statements to the beginning of a file, you can use
64//! [swc_ecma_utils::prepend_stmts] or [swc_ecma_utils::prepend] if `len == 1`.
65//!
66//! These methods are aware of the fact that `"use strict"` directive should be
67//! first in a file, and insert statements after directives.
68//!
69//! ### Improving readability
70//!
71//! Each stuffs are documented at itself.
72//!
73//!  - If you are creating or binding an [swc_ecma_ast::Expr] with operator, you
74//!    can use [swc_ecma_ast::op].
75//!
76//!  - If you want to create [swc_ecma_ast::CallExpr], you can use
77//!    [swc_ecma_utils::ExprFactory::as_callee] to create `callee`.
78//!
79//!  - If you want to create [swc_ecma_ast::CallExpr] or
80//!    [swc_ecma_ast::NewExpr], you can use
81//!    [swc_ecma_utils::ExprFactory::as_arg] to create arguments.
82//!
83//!
84//!  - If you want to create [swc_ecma_ast::MemberExpr] where all identifiers
85//!    are static (e.g. `Object.prototype.hasOwnProperty`), you can use
86//!    [swc_ecma_utils::member_expr].
87//!
88//!  - If you want to create [swc_ecma_ast::MemberExpr], you can use
89//!    [swc_ecma_utils::ExprFactory::as_obj] to create object field.
90//!
91//!
92//! ### Reducing binary size
93//!
94//! The visitor expands to a lot of code. You can reduce it by using macros like
95//!
96//!  - [noop_fold_type](swc_ecma_visit::noop_fold_type)
97//!  - [noop_visit_mut_type](swc_ecma_visit::noop_visit_mut_type)
98//!  - [noop_visit_type](swc_ecma_visit::noop_visit_type)
99//!
100//! Note that this will make typescript-related nodes not processed, but it's
101//! typically fine as `typescript::strip` is invoked at the start and it removes
102//! typescript-specific nodes.
103//!
104//! ### Porting `expr.evaluate()` of babel
105//!
106//! See [swc_ecma_minifier::eval::Evaluator].
107#![deny(unused)]
108#![allow(clippy::too_many_arguments)]
109#![allow(clippy::mutable_key_type)]
110#![cfg_attr(docsrs, feature(doc_cfg))]
111
112pub extern crate swc_atoms as atoms;
113extern crate swc_common as common;
114
115use std::{
116    cell::RefCell,
117    fs::{read_to_string, File},
118    io::ErrorKind,
119    path::{Path, PathBuf},
120    sync::Arc,
121};
122
123use anyhow::{bail, Context, Error};
124use base64::prelude::{Engine, BASE64_STANDARD};
125use common::{
126    comments::{Comment, SingleThreadedComments},
127    errors::HANDLER,
128};
129use jsonc_parser::{parse_to_serde_value, ParseOptions};
130use once_cell::sync::Lazy;
131use serde_json::error::Category;
132use swc_common::{
133    comments::Comments, errors::Handler, sync::Lrc, FileName, Mark, SourceFile, SourceMap, Spanned,
134    GLOBALS,
135};
136pub use swc_compiler_base::{PrintArgs, TransformOutput};
137pub use swc_config::types::{BoolConfig, BoolOr, BoolOrDataConfig};
138use swc_ecma_ast::{noop_pass, EsVersion, Pass, Program};
139use swc_ecma_codegen::{to_code_with_comments, Node};
140use swc_ecma_loader::resolvers::{
141    lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
142};
143use swc_ecma_minifier::option::{MangleCache, MinifyOptions, TopLevelOptions};
144use swc_ecma_parser::{EsSyntax, Syntax};
145use swc_ecma_transforms::{
146    fixer,
147    helpers::{self, Helpers},
148    hygiene,
149    modules::{path::NodeImportResolver, rewriter::import_rewriter},
150    resolver,
151};
152use swc_ecma_transforms_base::fixer::paren_remover;
153use swc_ecma_visit::{FoldWith, VisitMutWith, VisitWith};
154pub use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
155pub use swc_node_comments::SwcComments;
156pub use swc_sourcemap as sourcemap;
157use swc_timer::timer;
158use swc_transform_common::output::experimental_emit;
159use swc_typescript::fast_dts::FastDts;
160use tracing::warn;
161use url::Url;
162
163use crate::config::{
164    BuiltInput, Config, ConfigFile, InputSourceMap, IsModule, JsMinifyCommentOption,
165    JsMinifyOptions, Options, OutputCharset, Rc, RootMode, SourceMapsConfig,
166};
167
168mod builder;
169pub mod config;
170mod dropped_comments_preserver;
171mod plugin;
172pub mod wasm_analysis;
173pub mod resolver {
174    use std::path::PathBuf;
175
176    use rustc_hash::FxHashMap;
177    use swc_ecma_loader::{
178        resolvers::{lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver},
179        TargetEnv,
180    };
181
182    use crate::config::CompiledPaths;
183
184    pub type NodeResolver = CachingResolver<NodeModulesResolver>;
185
186    pub fn paths_resolver(
187        target_env: TargetEnv,
188        alias: FxHashMap<String, String>,
189        base_url: PathBuf,
190        paths: CompiledPaths,
191        preserve_symlinks: bool,
192    ) -> CachingResolver<TsConfigResolver<NodeModulesResolver>> {
193        let r = TsConfigResolver::new(
194            NodeModulesResolver::without_node_modules(target_env, alias, preserve_symlinks),
195            base_url,
196            paths,
197        );
198        CachingResolver::new(40, r)
199    }
200
201    pub fn environment_resolver(
202        target_env: TargetEnv,
203        alias: FxHashMap<String, String>,
204        preserve_symlinks: bool,
205    ) -> NodeResolver {
206        CachingResolver::new(
207            40,
208            NodeModulesResolver::new(target_env, alias, preserve_symlinks),
209        )
210    }
211}
212
213type SwcImportResolver = Arc<
214    NodeImportResolver<CachingResolver<TsConfigResolver<CachingResolver<NodeModulesResolver>>>>,
215>;
216
217/// All methods accept [Handler], which is a storage for errors.
218///
219/// The caller should check if the handler contains any errors after calling
220/// method.
221pub struct Compiler {
222    /// CodeMap
223    pub cm: Arc<SourceMap>,
224    comments: SwcComments,
225}
226
227/// These are **low-level** apis.
228impl Compiler {
229    pub fn comments(&self) -> &SwcComments {
230        &self.comments
231    }
232
233    /// Runs `op` in current compiler's context.
234    ///
235    /// Note: Other methods of `Compiler` already uses this internally.
236    pub fn run<R, F>(&self, op: F) -> R
237    where
238        F: FnOnce() -> R,
239    {
240        debug_assert!(
241            GLOBALS.is_set(),
242            "`swc_common::GLOBALS` is required for this operation"
243        );
244
245        op()
246    }
247
248    fn get_orig_src_map(
249        &self,
250        fm: &SourceFile,
251        input_src_map: &InputSourceMap,
252        comments: &[Comment],
253        is_default: bool,
254    ) -> Result<Option<sourcemap::SourceMap>, Error> {
255        self.run(|| -> Result<_, Error> {
256            let name = &fm.name;
257
258            let read_inline_sourcemap =
259                |data_url: &str| -> Result<Option<sourcemap::SourceMap>, Error> {
260                    let url = Url::parse(data_url).with_context(|| {
261                        format!("failed to parse inline source map url\n{data_url}")
262                    })?;
263
264                    let idx = match url.path().find("base64,") {
265                        Some(v) => v,
266                        None => {
267                            bail!("failed to parse inline source map: not base64: {:?}", url)
268                        }
269                    };
270
271                    let content = url.path()[idx + "base64,".len()..].trim();
272
273                    let res = BASE64_STANDARD
274                        .decode(content.as_bytes())
275                        .context("failed to decode base64-encoded source map")?;
276
277                    Ok(Some(sourcemap::SourceMap::from_slice(&res).context(
278                        "failed to read input source map from inlined base64 encoded string",
279                    )?))
280                };
281
282            let read_file_sourcemap =
283                |data_url: Option<&str>| -> Result<Option<sourcemap::SourceMap>, Error> {
284                    match &**name {
285                        FileName::Real(filename) => {
286                            let dir = match filename.parent() {
287                                Some(v) => v,
288                                None => {
289                                    bail!("unexpected: root directory is given as a input file")
290                                }
291                            };
292
293                            let map_path = match data_url {
294                                Some(data_url) => {
295                                    let mut map_path = dir.join(data_url);
296                                    if !map_path.exists() {
297                                        // Old behavior. This check would prevent
298                                        // regressions.
299                                        // Perhaps it shouldn't be supported. Sometimes
300                                        // developers don't want to expose their source
301                                        // code.
302                                        // Map files are for internal troubleshooting
303                                        // convenience.
304                                        let fallback_map_path =
305                                            PathBuf::from(format!("{}.map", filename.display()));
306                                        if fallback_map_path.exists() {
307                                            map_path = fallback_map_path;
308                                        } else {
309                                            bail!(
310                                                "failed to find input source map file {:?} in \
311                                                 {:?} file as either {:?} or with appended .map",
312                                                data_url,
313                                                filename.display(),
314                                                map_path.display(),
315                                            )
316                                        }
317                                    }
318
319                                    Some(map_path)
320                                }
321                                None => {
322                                    // Old behavior.
323                                    let map_path =
324                                        PathBuf::from(format!("{}.map", filename.display()));
325                                    if map_path.exists() {
326                                        Some(map_path)
327                                    } else {
328                                        None
329                                    }
330                                }
331                            };
332
333                            match map_path {
334                                Some(map_path) => {
335                                    let path = map_path.display().to_string();
336                                    let file = File::open(&path);
337
338                                    // If file is not found, we should return None.
339                                    // Some libraries generates source map but omit them from the
340                                    // npm package.
341                                    //
342                                    // See https://github.com/swc-project/swc/issues/8789#issuecomment-2105055772
343                                    if file
344                                        .as_ref()
345                                        .is_err_and(|err| err.kind() == ErrorKind::NotFound)
346                                    {
347                                        warn!(
348                                            "source map is specified by sourceMappingURL but \
349                                             there's no source map at `{}`",
350                                            path
351                                        );
352                                        return Ok(None);
353                                    }
354
355                                    // Old behavior.
356                                    let file = if !is_default {
357                                        file?
358                                    } else {
359                                        match file {
360                                            Ok(v) => v,
361                                            Err(_) => return Ok(None),
362                                        }
363                                    };
364
365                                    Ok(Some(sourcemap::SourceMap::from_reader(file).with_context(
366                                        || {
367                                            format!(
368                                                "failed to read input source map
369                                from file at {path}"
370                                            )
371                                        },
372                                    )?))
373                                }
374                                None => Ok(None),
375                            }
376                        }
377                        _ => Ok(None),
378                    }
379                };
380
381            let read_sourcemap = || -> Option<sourcemap::SourceMap> {
382                let s = "sourceMappingURL=";
383
384                let text = comments.iter().rev().find_map(|c| {
385                    let idx = c.text.rfind(s)?;
386                    let (_, url) = c.text.split_at(idx + s.len());
387
388                    Some(url.trim())
389                });
390
391                // Load original source map if possible
392                let result = match text {
393                    Some(text) if text.starts_with("data:") => read_inline_sourcemap(text),
394                    _ => read_file_sourcemap(text),
395                };
396                match result {
397                    Ok(r) => r,
398                    Err(err) => {
399                        tracing::error!("failed to read input source map: {:?}", err);
400                        None
401                    }
402                }
403            };
404
405            // Load original source map
406            match input_src_map {
407                InputSourceMap::Bool(false) => Ok(None),
408                InputSourceMap::Bool(true) => Ok(read_sourcemap()),
409                InputSourceMap::Str(ref s) => {
410                    if s == "inline" {
411                        Ok(read_sourcemap())
412                    } else {
413                        // Load source map passed by user
414                        Ok(Some(
415                            swc_sourcemap::SourceMap::from_slice(s.as_bytes()).context(
416                                "failed to read input source map from user-provided sourcemap",
417                            )?,
418                        ))
419                    }
420                }
421            }
422        })
423    }
424
425    /// This method parses a javascript / typescript file
426    pub fn parse_js(
427        &self,
428        fm: Arc<SourceFile>,
429        handler: &Handler,
430        target: EsVersion,
431        syntax: Syntax,
432        is_module: IsModule,
433        comments: Option<&dyn Comments>,
434    ) -> Result<Program, Error> {
435        swc_compiler_base::parse_js(
436            self.cm.clone(),
437            fm,
438            handler,
439            target,
440            syntax,
441            is_module,
442            comments,
443        )
444    }
445
446    /// Converts ast node to source string and sourcemap.
447    ///
448    ///
449    /// This method receives target file path, but does not write file to the
450    /// path. See: https://github.com/swc-project/swc/issues/1255
451    #[allow(clippy::too_many_arguments)]
452    pub fn print<T>(&self, node: &T, args: PrintArgs) -> Result<TransformOutput, Error>
453    where
454        T: Node + VisitWith<swc_compiler_base::IdentCollector>,
455    {
456        swc_compiler_base::print(self.cm.clone(), node, args)
457    }
458}
459
460/// High-level apis.
461impl Compiler {
462    pub fn new(cm: Arc<SourceMap>) -> Self {
463        Compiler {
464            cm,
465            comments: Default::default(),
466        }
467    }
468
469    #[tracing::instrument(skip_all)]
470    pub fn read_config(&self, opts: &Options, name: &FileName) -> Result<Option<Config>, Error> {
471        static CUR_DIR: Lazy<PathBuf> = Lazy::new(|| {
472            if cfg!(target_arch = "wasm32") {
473                PathBuf::new()
474            } else {
475                ::std::env::current_dir().unwrap()
476            }
477        });
478
479        self.run(|| -> Result<_, Error> {
480            let Options {
481                ref root,
482                root_mode,
483                swcrc,
484                config_file,
485                ..
486            } = opts;
487
488            let root = root.as_ref().unwrap_or(&CUR_DIR);
489
490            let swcrc_path = match config_file {
491                Some(ConfigFile::Str(s)) => Some(PathBuf::from(s.clone())),
492                _ => {
493                    if *swcrc {
494                        if let FileName::Real(ref path) = name {
495                            find_swcrc(path, root, *root_mode)
496                        } else {
497                            None
498                        }
499                    } else {
500                        None
501                    }
502                }
503            };
504
505            let config_file = match swcrc_path.as_deref() {
506                Some(s) => Some(load_swcrc(s)?),
507                _ => None,
508            };
509            let filename_path = match name {
510                FileName::Real(p) => Some(&**p),
511                _ => None,
512            };
513
514            if let Some(filename_path) = filename_path {
515                if let Some(config) = config_file {
516                    let dir = swcrc_path
517                        .as_deref()
518                        .and_then(|p| p.parent())
519                        .expect(".swcrc path should have parent dir");
520
521                    let mut config = config
522                        .into_config(Some(filename_path))
523                        .context("failed to process config file")?;
524
525                    if let Some(c) = &mut config {
526                        if c.jsc.base_url != PathBuf::new() {
527                            let joined = dir.join(&c.jsc.base_url);
528                            c.jsc.base_url = if cfg!(target_os = "windows")
529                                && c.jsc.base_url.as_os_str() == "."
530                            {
531                                dir.canonicalize().with_context(|| {
532                                    format!(
533                                        "failed to canonicalize base url using the path of \
534                                         .swcrc\nDir: {}\n(Used logic for windows)",
535                                        dir.display(),
536                                    )
537                                })?
538                            } else {
539                                joined.canonicalize().with_context(|| {
540                                    format!(
541                                        "failed to canonicalize base url using the path of \
542                                         .swcrc\nPath: {}\nDir: {}\nbaseUrl: {}",
543                                        joined.display(),
544                                        dir.display(),
545                                        c.jsc.base_url.display()
546                                    )
547                                })?
548                            };
549                        }
550                    }
551
552                    return Ok(config);
553                }
554
555                let config_file = config_file.unwrap_or_default();
556                let config = config_file.into_config(Some(filename_path))?;
557
558                return Ok(config);
559            }
560
561            let config = match config_file {
562                Some(config_file) => config_file.into_config(None)?,
563                None => Rc::default().into_config(None)?,
564            };
565
566            match config {
567                Some(config) => Ok(Some(config)),
568                None => {
569                    bail!("no config matched for file ({})", name)
570                }
571            }
572        })
573        .with_context(|| format!("failed to read .swcrc file for input file at `{name}`"))
574    }
575
576    /// This method returns [None] if a file should be skipped.
577    ///
578    /// This method handles merging of config.
579    ///
580    /// This method does **not** parse module.
581    #[tracing::instrument(skip_all)]
582    pub fn parse_js_as_input<'a, P>(
583        &'a self,
584        fm: Lrc<SourceFile>,
585        program: Option<Program>,
586        handler: &'a Handler,
587        opts: &Options,
588        name: &FileName,
589        comments: Option<&'a SingleThreadedComments>,
590        before_pass: impl 'a + FnOnce(&Program) -> P,
591    ) -> Result<Option<BuiltInput<impl 'a + Pass>>, Error>
592    where
593        P: 'a + Pass,
594    {
595        self.run(move || {
596            let _timer = timer!("Compiler.parse");
597
598            if let FileName::Real(ref path) = name {
599                if !opts.config.matches(path)? {
600                    return Ok(None);
601                }
602            }
603
604            let config = self.read_config(opts, name)?;
605            let config = match config {
606                Some(v) => v,
607                None => return Ok(None),
608            };
609
610            let built = opts.build_as_input(
611                &self.cm,
612                name,
613                move |syntax, target, is_module| match program {
614                    Some(v) => Ok(v),
615                    _ => self.parse_js(
616                        fm.clone(),
617                        handler,
618                        target,
619                        syntax,
620                        is_module,
621                        comments.as_ref().map(|v| v as _),
622                    ),
623                },
624                opts.output_path.as_deref(),
625                opts.source_root.clone(),
626                opts.source_file_name.clone(),
627                config.source_map_ignore_list.clone(),
628                handler,
629                Some(config),
630                comments,
631                before_pass,
632            )?;
633            Ok(Some(built))
634        })
635    }
636
637    pub fn run_transform<F, Ret>(&self, handler: &Handler, external_helpers: bool, op: F) -> Ret
638    where
639        F: FnOnce() -> Ret,
640    {
641        self.run(|| {
642            helpers::HELPERS.set(&Helpers::new(external_helpers), || HANDLER.set(handler, op))
643        })
644    }
645
646    #[tracing::instrument(skip_all)]
647    pub fn transform(
648        &self,
649        handler: &Handler,
650        program: Program,
651        external_helpers: bool,
652        mut pass: impl swc_ecma_visit::Fold,
653    ) -> Program {
654        self.run_transform(handler, external_helpers, || {
655            // Fold module
656            program.fold_with(&mut pass)
657        })
658    }
659
660    /// `custom_after_pass` is applied after swc transforms are applied.
661    ///
662    /// `program`: If you already parsed `Program`, you can pass it.
663    ///
664    /// # Guarantee
665    ///
666    /// `swc` invokes `custom_before_pass` after
667    ///
668    ///  - Handling decorators, if configured
669    ///  - Applying `resolver`
670    ///  - Stripping typescript nodes
671    ///
672    /// This means, you can use `noop_visit_type`, `noop_fold_type` and
673    /// `noop_visit_mut_type` in your visitor to reduce the binary size.
674    #[tracing::instrument(skip_all)]
675    pub fn process_js_with_custom_pass<P1, P2>(
676        &self,
677        fm: Arc<SourceFile>,
678        program: Option<Program>,
679        handler: &Handler,
680        opts: &Options,
681        comments: SingleThreadedComments,
682        custom_before_pass: impl FnOnce(&Program) -> P1,
683        custom_after_pass: impl FnOnce(&Program) -> P2,
684    ) -> Result<TransformOutput, Error>
685    where
686        P1: Pass,
687        P2: Pass,
688    {
689        self.run(|| -> Result<_, Error> {
690            let config = self.run(|| {
691                self.parse_js_as_input(
692                    fm.clone(),
693                    program,
694                    handler,
695                    opts,
696                    &fm.name,
697                    Some(&comments),
698                    |program| custom_before_pass(program),
699                )
700            })?;
701            let config = match config {
702                Some(v) => v,
703                None => {
704                    bail!("cannot process file because it's ignored by .swcrc")
705                }
706            };
707
708            let after_pass = custom_after_pass(&config.program);
709
710            let config = config.with_pass(|pass| (pass, after_pass));
711
712            let orig = if config.source_maps.enabled() {
713                self.get_orig_src_map(
714                    &fm,
715                    &config.input_source_map,
716                    config
717                        .comments
718                        .get_trailing(config.program.span_hi())
719                        .as_deref()
720                        .unwrap_or_default(),
721                    false,
722                )?
723            } else {
724                None
725            };
726
727            self.apply_transforms(handler, comments.clone(), fm.clone(), orig, config)
728        })
729    }
730
731    #[tracing::instrument(skip(self, handler, opts))]
732    pub fn process_js_file(
733        &self,
734        fm: Arc<SourceFile>,
735        handler: &Handler,
736        opts: &Options,
737    ) -> Result<TransformOutput, Error> {
738        self.process_js_with_custom_pass(
739            fm,
740            None,
741            handler,
742            opts,
743            SingleThreadedComments::default(),
744            |_| noop_pass(),
745            |_| noop_pass(),
746        )
747    }
748
749    #[tracing::instrument(skip_all)]
750    pub fn minify(
751        &self,
752        fm: Arc<SourceFile>,
753        handler: &Handler,
754        opts: &JsMinifyOptions,
755        extras: JsMinifyExtras,
756    ) -> Result<TransformOutput, Error> {
757        self.run(|| {
758            let _timer = timer!("Compiler::minify");
759
760            let target = opts.ecma.clone().into();
761
762            let (source_map, orig, source_map_url) = opts
763                .source_map
764                .as_ref()
765                .map(|obj| -> Result<_, Error> {
766                    let orig = obj.content.as_ref().map(|s| s.to_sourcemap()).transpose()?;
767
768                    Ok((SourceMapsConfig::Bool(true), orig, obj.url.as_deref()))
769                })
770                .unwrap_as_option(|v| {
771                    Some(Ok(match v {
772                        Some(true) => (SourceMapsConfig::Bool(true), None, None),
773                        _ => (SourceMapsConfig::Bool(false), None, None),
774                    }))
775                })
776                .unwrap()?;
777
778            let mut min_opts = MinifyOptions {
779                compress: opts
780                    .compress
781                    .clone()
782                    .unwrap_as_option(|default| match default {
783                        Some(true) | None => Some(Default::default()),
784                        _ => None,
785                    })
786                    .map(|v| v.into_config(self.cm.clone())),
787                mangle: opts
788                    .mangle
789                    .clone()
790                    .unwrap_as_option(|default| match default {
791                        Some(true) | None => Some(Default::default()),
792                        _ => None,
793                    }),
794                ..Default::default()
795            };
796
797            // top_level defaults to true if module is true
798
799            // https://github.com/swc-project/swc/issues/2254
800
801            if opts.keep_fnames {
802                if let Some(opts) = &mut min_opts.compress {
803                    opts.keep_fnames = true;
804                }
805                if let Some(opts) = &mut min_opts.mangle {
806                    opts.keep_fn_names = true;
807                }
808            }
809
810            let comments = SingleThreadedComments::default();
811
812            let mut program = self
813                .parse_js(
814                    fm.clone(),
815                    handler,
816                    target,
817                    Syntax::Es(EsSyntax {
818                        jsx: true,
819                        decorators: true,
820                        decorators_before_export: true,
821                        import_attributes: true,
822                        ..Default::default()
823                    }),
824                    opts.module,
825                    Some(&comments),
826                )
827                .context("failed to parse input file")?;
828
829            if program.is_module() {
830                if let Some(opts) = &mut min_opts.compress {
831                    if opts.top_level.is_none() {
832                        opts.top_level = Some(TopLevelOptions { functions: true });
833                    }
834                }
835
836                if let Some(opts) = &mut min_opts.mangle {
837                    if opts.top_level.is_none() {
838                        opts.top_level = Some(true);
839                    }
840                }
841            }
842
843            let source_map_names = if source_map.enabled() {
844                let mut v = swc_compiler_base::IdentCollector {
845                    names: Default::default(),
846                };
847
848                program.visit_with(&mut v);
849
850                v.names
851            } else {
852                Default::default()
853            };
854
855            let unresolved_mark = Mark::new();
856            let top_level_mark = Mark::new();
857
858            let is_mangler_enabled = min_opts.mangle.is_some();
859
860            program = self.run_transform(handler, false, || {
861                program.mutate(&mut paren_remover(Some(&comments)));
862
863                program.mutate(&mut resolver(unresolved_mark, top_level_mark, false));
864
865                let mut program = swc_ecma_minifier::optimize(
866                    program,
867                    self.cm.clone(),
868                    Some(&comments),
869                    None,
870                    &min_opts,
871                    &swc_ecma_minifier::option::ExtraOptions {
872                        unresolved_mark,
873                        top_level_mark,
874                        mangle_name_cache: extras.mangle_name_cache,
875                    },
876                );
877
878                if !is_mangler_enabled {
879                    program.visit_mut_with(&mut hygiene())
880                }
881                program.mutate(&mut fixer(Some(&comments as &dyn Comments)));
882                program
883            });
884
885            let preserve_comments = opts
886                .format
887                .comments
888                .clone()
889                .into_inner()
890                .unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments));
891            swc_compiler_base::minify_file_comments(
892                &comments,
893                preserve_comments,
894                opts.format.preserve_annotations,
895            );
896
897            let ret = self.print(
898                &program,
899                PrintArgs {
900                    source_root: None,
901                    source_file_name: Some(&fm.name.to_string()),
902                    output_path: opts.output_path.clone().map(From::from),
903                    inline_sources_content: opts.inline_sources_content,
904                    source_map,
905                    source_map_ignore_list: opts.source_map_ignore_list.clone(),
906                    source_map_names: &source_map_names,
907                    orig,
908                    comments: Some(&comments),
909                    emit_source_map_columns: opts.emit_source_map_columns,
910                    preamble: &opts.format.preamble,
911                    codegen_config: swc_ecma_codegen::Config::default()
912                        .with_target(target)
913                        .with_minify(true)
914                        .with_ascii_only(opts.format.ascii_only)
915                        .with_emit_assert_for_import_attributes(
916                            opts.format.emit_assert_for_import_attributes,
917                        )
918                        .with_inline_script(opts.format.inline_script)
919                        .with_reduce_escaped_newline(
920                            min_opts
921                                .compress
922                                .unwrap_or_default()
923                                .experimental
924                                .reduce_escaped_newline,
925                        ),
926                    output: None,
927                    source_map_url,
928                },
929            );
930
931            ret.map(|mut output| {
932                output.diagnostics = handler.take_diagnostics();
933
934                output
935            })
936        })
937    }
938
939    /// You can use custom pass with this method.
940    ///
941    /// Pass building logic has been inlined into the configuration system.
942    #[tracing::instrument(skip_all)]
943    pub fn process_js(
944        &self,
945        handler: &Handler,
946        program: Program,
947        opts: &Options,
948    ) -> Result<TransformOutput, Error> {
949        let loc = self.cm.lookup_char_pos(program.span().lo());
950        let fm = loc.file;
951
952        self.process_js_with_custom_pass(
953            fm,
954            Some(program),
955            handler,
956            opts,
957            SingleThreadedComments::default(),
958            |_| noop_pass(),
959            |_| noop_pass(),
960        )
961    }
962
963    #[tracing::instrument(name = "swc::Compiler::apply_transforms", skip_all)]
964    fn apply_transforms(
965        &self,
966        handler: &Handler,
967        comments: SingleThreadedComments,
968        fm: Arc<SourceFile>,
969        orig: Option<sourcemap::SourceMap>,
970        config: BuiltInput<impl Pass>,
971    ) -> Result<TransformOutput, Error> {
972        self.run(|| {
973            let program = config.program;
974
975            if config.emit_isolated_dts && !config.syntax.typescript() {
976                handler.warn(
977                    "jsc.experimental.emitIsolatedDts is enabled but the syntax is not TypeScript",
978                );
979            }
980
981            let emit_dts = config.syntax.typescript() && config.emit_isolated_dts;
982            let source_map_names = if config.source_maps.enabled() {
983                let mut v = swc_compiler_base::IdentCollector {
984                    names: Default::default(),
985                };
986
987                program.visit_with(&mut v);
988
989                v.names
990            } else {
991                Default::default()
992            };
993
994            let dts_code = if emit_dts {
995                let (leading, trailing) = comments.borrow_all();
996
997                let leading = std::rc::Rc::new(RefCell::new(leading.clone()));
998                let trailing = std::rc::Rc::new(RefCell::new(trailing.clone()));
999
1000                let comments = SingleThreadedComments::from_leading_and_trailing(leading, trailing);
1001                let mut checker =
1002                    FastDts::new(fm.name.clone(), config.unresolved_mark, Default::default());
1003                let mut program = program.clone();
1004
1005                if let Some((base, resolver)) = config.resolver {
1006                    program.mutate(import_rewriter(base, resolver));
1007                }
1008
1009                let issues = checker.transform(&mut program);
1010
1011                for issue in issues {
1012                    handler
1013                        .struct_span_err(issue.range.span, &issue.message)
1014                        .emit();
1015                }
1016
1017                let dts_code = to_code_with_comments(Some(&comments), &program);
1018                Some(dts_code)
1019            } else {
1020                None
1021            };
1022
1023            let pass = config.pass;
1024            let (program, output) = swc_transform_common::output::capture(|| {
1025                if let Some(dts_code) = dts_code {
1026                    experimental_emit("__swc_isolated_declarations__".into(), dts_code);
1027                }
1028
1029                helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
1030                    HANDLER.set(handler, || {
1031                        // Fold module
1032                        program.apply(pass)
1033                    })
1034                })
1035            });
1036
1037            if let Some(comments) = &config.comments {
1038                swc_compiler_base::minify_file_comments(
1039                    comments,
1040                    config.preserve_comments,
1041                    config.output.preserve_annotations.into_bool(),
1042                );
1043            }
1044
1045            self.print(
1046                &program,
1047                PrintArgs {
1048                    source_root: config.source_root.as_deref(),
1049                    source_file_name: config.source_file_name.as_deref(),
1050                    source_map_ignore_list: config.source_map_ignore_list.clone(),
1051                    output_path: config.output_path,
1052                    inline_sources_content: config.inline_sources_content,
1053                    source_map: config.source_maps,
1054                    source_map_names: &source_map_names,
1055                    orig,
1056                    comments: config.comments.as_ref().map(|v| v as _),
1057                    emit_source_map_columns: config.emit_source_map_columns,
1058                    preamble: &config.output.preamble,
1059                    codegen_config: swc_ecma_codegen::Config::default()
1060                        .with_target(config.target)
1061                        .with_minify(config.minify)
1062                        .with_ascii_only(
1063                            config
1064                                .output
1065                                .charset
1066                                .map(|v| matches!(v, OutputCharset::Ascii))
1067                                .unwrap_or(false),
1068                        )
1069                        .with_emit_assert_for_import_attributes(
1070                            config.emit_assert_for_import_attributes,
1071                        )
1072                        .with_inline_script(config.codegen_inline_script),
1073                    output: if output.is_empty() {
1074                        None
1075                    } else {
1076                        Some(output)
1077                    },
1078                    source_map_url: config.output.source_map_url.as_deref(),
1079                },
1080            )
1081        })
1082    }
1083}
1084
1085#[non_exhaustive]
1086#[derive(Clone, Default)]
1087pub struct JsMinifyExtras {
1088    pub mangle_name_cache: Option<Arc<dyn MangleCache>>,
1089}
1090
1091impl JsMinifyExtras {
1092    pub fn with_mangle_name_cache(
1093        mut self,
1094        mangle_name_cache: Option<Arc<dyn MangleCache>>,
1095    ) -> Self {
1096        self.mangle_name_cache = mangle_name_cache;
1097        self
1098    }
1099}
1100
1101fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option<PathBuf> {
1102    let mut parent = path.parent();
1103    while let Some(dir) = parent {
1104        let swcrc = dir.join(".swcrc");
1105
1106        if swcrc.exists() {
1107            return Some(swcrc);
1108        }
1109
1110        if dir == root && root_mode == RootMode::Root {
1111            break;
1112        }
1113        parent = dir.parent();
1114    }
1115
1116    None
1117}
1118
1119#[tracing::instrument(skip_all)]
1120fn load_swcrc(path: &Path) -> Result<Rc, Error> {
1121    let content = read_to_string(path).context("failed to read config (.swcrc) file")?;
1122
1123    parse_swcrc(&content)
1124}
1125
1126fn parse_swcrc(s: &str) -> Result<Rc, Error> {
1127    fn convert_json_err(e: serde_json::Error) -> Error {
1128        let line = e.line();
1129        let column = e.column();
1130
1131        let msg = match e.classify() {
1132            Category::Io => "io error",
1133            Category::Syntax => "syntax error",
1134            Category::Data => "unmatched data",
1135            Category::Eof => "unexpected eof",
1136        };
1137        Error::new(e).context(format!(
1138            "failed to deserialize .swcrc (json) file: {msg}: {line}:{column}"
1139        ))
1140    }
1141
1142    let v = parse_to_serde_value(
1143        s.trim_start_matches('\u{feff}'),
1144        &ParseOptions {
1145            allow_comments: true,
1146            allow_trailing_commas: true,
1147            allow_loose_object_property_names: false,
1148        },
1149    )?
1150    .ok_or_else(|| Error::msg("failed to deserialize empty .swcrc (json) file"))?;
1151
1152    if let Ok(rc) = serde_json::from_value(v.clone()) {
1153        return Ok(rc);
1154    }
1155
1156    serde_json::from_value(v)
1157        .map(Rc::Single)
1158        .map_err(convert_json_err)
1159}