swc/config/
mod.rs

1use std::{
2    collections::{HashMap, HashSet},
3    env,
4    path::{Path, PathBuf},
5    sync::Arc,
6};
7
8use anyhow::{bail, Context, Error};
9use dashmap::DashMap;
10use indexmap::IndexMap;
11use once_cell::sync::Lazy;
12use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
13use serde::{Deserialize, Serialize};
14use swc_atoms::Atom;
15use swc_cached::regex::CachedRegex;
16#[allow(unused)]
17use swc_common::plugin::metadata::TransformPluginMetadataContext;
18use swc_common::{
19    comments::{Comments, SingleThreadedComments},
20    errors::Handler,
21    FileName, Mark, SourceMap, SyntaxContext,
22};
23pub use swc_compiler_base::{IsModule, SourceMapsConfig};
24use swc_config::{
25    config_types::{BoolConfig, BoolOr, BoolOrDataConfig, MergingOption},
26    merge::Merge,
27};
28use swc_ecma_ast::{noop_pass, EsVersion, Expr, Pass, Program};
29use swc_ecma_ext_transforms::jest;
30use swc_ecma_lints::{
31    config::LintConfig,
32    rules::{lint_pass, LintParams},
33};
34use swc_ecma_loader::resolvers::{
35    lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
36};
37pub use swc_ecma_minifier::js::*;
38use swc_ecma_minifier::option::terser::TerserTopLevelOptions;
39use swc_ecma_parser::{parse_file_as_expr, Syntax, TsSyntax};
40pub use swc_ecma_transforms::proposals::DecoratorVersion;
41use swc_ecma_transforms::{
42    feature::FeatureFlag,
43    hygiene,
44    modules::{
45        self,
46        path::{ImportResolver, NodeImportResolver, Resolver},
47        rewriter::import_rewriter,
48        util, EsModuleConfig,
49    },
50    optimization::{const_modules, json_parse, simplifier},
51    proposals::{
52        decorators, explicit_resource_management::explicit_resource_management,
53        export_default_from, import_attributes,
54    },
55    react::{self, default_pragma, default_pragma_frag},
56    resolver,
57    typescript::{self, TsImportExportAssignConfig},
58    Assumptions,
59};
60use swc_ecma_transforms_compat::es2015::regenerator;
61use swc_ecma_transforms_optimization::{
62    inline_globals2,
63    simplify::{dce::Config as DceConfig, Config as SimplifyConfig},
64    GlobalExprMap,
65};
66use swc_ecma_utils::NodeIgnoringSpan;
67use swc_ecma_visit::VisitMutWith;
68use swc_visit::Optional;
69
70pub use crate::plugin::PluginConfig;
71use crate::{
72    builder::PassBuilder, dropped_comments_preserver::dropped_comments_preserver, SwcImportResolver,
73};
74
75#[cfg(test)]
76mod tests;
77
78#[cfg(feature = "plugin")]
79/// A shared instance to plugin's module bytecode cache.
80pub static PLUGIN_MODULE_CACHE: Lazy<swc_plugin_runner::cache::PluginModuleCache> =
81    Lazy::new(Default::default);
82
83/// Create a new cache instance if not initialized. This can be called multiple
84/// time, but any subsequent call will be ignored.
85///
86/// This fn have a side effect to create path to cache if given path is not
87/// resolvable if fs_cache_store is enabled. If root is not specified, it'll
88/// generate default root for cache location.
89///
90/// If cache failed to initialize filesystem cache for given location
91/// it'll be serve in-memory cache only.
92#[cfg(feature = "plugin")]
93pub fn init_plugin_module_cache_once(
94    enable_fs_cache_store: bool,
95    fs_cache_store_root: &Option<String>,
96) {
97    PLUGIN_MODULE_CACHE.inner.get_or_init(|| {
98        parking_lot::Mutex::new(swc_plugin_runner::cache::PluginModuleCache::create_inner(
99            enable_fs_cache_store,
100            fs_cache_store_root,
101        ))
102    });
103}
104
105#[derive(Default, Clone, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct ParseOptions {
108    #[serde(default)]
109    pub comments: bool,
110    #[serde(flatten)]
111    pub syntax: Syntax,
112
113    #[serde(default)]
114    pub is_module: IsModule,
115
116    #[serde(default)]
117    pub target: EsVersion,
118}
119
120#[derive(Debug, Clone, Default, Deserialize)]
121#[serde(deny_unknown_fields, rename_all = "camelCase")]
122pub struct Options {
123    #[serde(flatten)]
124    pub config: Config,
125
126    #[serde(skip_deserializing, default)]
127    pub skip_helper_injection: bool,
128
129    #[serde(skip_deserializing, default)]
130    pub disable_hygiene: bool,
131
132    #[serde(skip_deserializing, default)]
133    pub disable_fixer: bool,
134
135    #[serde(skip_deserializing, default)]
136    pub top_level_mark: Option<Mark>,
137
138    #[serde(skip_deserializing, default)]
139    pub unresolved_mark: Option<Mark>,
140
141    #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
142    #[serde(default = "default_cwd")]
143    pub cwd: PathBuf,
144
145    #[serde(default)]
146    pub caller: Option<CallerOptions>,
147
148    #[serde(default)]
149    pub filename: String,
150
151    #[serde(default)]
152    pub config_file: Option<ConfigFile>,
153
154    #[serde(default)]
155    pub root: Option<PathBuf>,
156
157    #[serde(default)]
158    pub root_mode: RootMode,
159
160    #[serde(default = "default_swcrc")]
161    pub swcrc: bool,
162
163    #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
164    #[serde(default)]
165    pub swcrc_roots: Option<PathBuf>,
166
167    #[serde(default = "default_env_name")]
168    pub env_name: String,
169
170    #[serde(default)]
171    pub source_maps: Option<SourceMapsConfig>,
172
173    #[serde(default)]
174    pub source_file_name: Option<String>,
175
176    #[serde(default)]
177    pub source_root: Option<String>,
178
179    #[serde(default)]
180    pub output_path: Option<PathBuf>,
181
182    #[serde(default)]
183    pub experimental: ExperimentalOptions,
184}
185
186#[derive(Debug, Clone, Default, Deserialize, Merge)]
187#[serde(deny_unknown_fields, rename_all = "camelCase")]
188pub struct ExperimentalOptions {
189    #[serde(default)]
190    pub error_format: Option<ErrorFormat>,
191}
192
193impl Options {
194    pub fn codegen_target(&self) -> Option<EsVersion> {
195        self.config.jsc.target
196    }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200#[serde(untagged)]
201pub enum InputSourceMap {
202    Bool(bool),
203    Str(String),
204}
205
206impl Default for InputSourceMap {
207    fn default() -> Self {
208        InputSourceMap::Bool(true)
209    }
210}
211
212impl Options {
213    /// `parse`: `(syntax, target, is_module)`
214    ///
215    /// `parse` should use `comments`.
216    #[allow(clippy::too_many_arguments)]
217    pub fn build_as_input<'a, P>(
218        &self,
219        cm: &Arc<SourceMap>,
220        base: &FileName,
221        parse: impl FnOnce(Syntax, EsVersion, IsModule) -> Result<Program, Error>,
222        output_path: Option<&Path>,
223        source_root: Option<String>,
224        source_file_name: Option<String>,
225        handler: &Handler,
226        config: Option<Config>,
227        comments: Option<&'a SingleThreadedComments>,
228        custom_before_pass: impl FnOnce(&Program) -> P,
229    ) -> Result<BuiltInput<Box<dyn 'a + Pass>>, Error>
230    where
231        P: 'a + Pass,
232    {
233        let mut cfg = self.config.clone();
234
235        cfg.merge(config.unwrap_or_default());
236
237        if let FileName::Real(base) = base {
238            cfg.adjust(base);
239        }
240
241        let is_module = cfg.is_module.unwrap_or_default();
242
243        let mut source_maps = self.source_maps.clone();
244        source_maps.merge(cfg.source_maps.clone());
245
246        let JscConfig {
247            assumptions,
248            transform,
249            syntax,
250            external_helpers,
251            target,
252            loose,
253            keep_class_names,
254            base_url,
255            paths,
256            minify: mut js_minify,
257            experimental,
258            lints,
259            preserve_all_comments,
260            ..
261        } = cfg.jsc;
262        let loose = loose.into_bool();
263        let preserve_all_comments = preserve_all_comments.into_bool();
264        let keep_class_names = keep_class_names.into_bool();
265        let external_helpers = external_helpers.into_bool();
266
267        let mut assumptions = assumptions.unwrap_or_else(|| {
268            if loose {
269                Assumptions::all()
270            } else {
271                Assumptions::default()
272            }
273        });
274
275        let unresolved_mark = self.unresolved_mark.unwrap_or_default();
276        let top_level_mark = self.top_level_mark.unwrap_or_default();
277
278        if target.is_some() && cfg.env.is_some() {
279            bail!("`env` and `jsc.target` cannot be used together");
280        }
281
282        let es_version = target.unwrap_or_default();
283
284        let syntax = syntax.unwrap_or_default();
285
286        let mut program = parse(syntax, es_version, is_module)?;
287
288        let mut transform = transform.into_inner().unwrap_or_default();
289
290        // Do a resolver pass before everything.
291        //
292        // We do this before creating custom passes, so custom passses can use the
293        // variable management system based on the syntax contexts.
294        if syntax.typescript() {
295            assumptions.set_class_methods |= !transform.use_define_for_class_fields.into_bool();
296        }
297
298        assumptions.set_public_class_fields |= !transform.use_define_for_class_fields.into_bool();
299
300        program.visit_mut_with(&mut resolver(
301            unresolved_mark,
302            top_level_mark,
303            syntax.typescript(),
304        ));
305
306        let default_top_level = program.is_module();
307
308        js_minify = js_minify.map(|mut c| {
309            let compress = c
310                .compress
311                .unwrap_as_option(|default| match default {
312                    Some(true) => Some(Default::default()),
313                    _ => None,
314                })
315                .map(|mut c| {
316                    if c.toplevel.is_none() {
317                        c.toplevel = Some(TerserTopLevelOptions::Bool(default_top_level));
318                    }
319
320                    if matches!(
321                        cfg.module,
322                        None | Some(ModuleConfig::Es6(..) | ModuleConfig::NodeNext(..))
323                    ) {
324                        c.module = true;
325                    }
326
327                    c
328                })
329                .map(BoolOrDataConfig::from_obj)
330                .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
331
332            let mangle = c
333                .mangle
334                .unwrap_as_option(|default| match default {
335                    Some(true) => Some(Default::default()),
336                    _ => None,
337                })
338                .map(|mut c| {
339                    if c.top_level.is_none() {
340                        c.top_level = Some(default_top_level);
341                    }
342
343                    c
344                })
345                .map(BoolOrDataConfig::from_obj)
346                .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
347
348            if c.toplevel.is_none() {
349                c.toplevel = Some(default_top_level);
350            }
351
352            JsMinifyOptions {
353                compress,
354                mangle,
355                ..c
356            }
357        });
358
359        if js_minify.is_some() && js_minify.as_ref().unwrap().keep_fnames {
360            js_minify = js_minify.map(|c| {
361                let compress = c
362                    .compress
363                    .unwrap_as_option(|default| match default {
364                        Some(true) => Some(Default::default()),
365                        _ => None,
366                    })
367                    .map(|mut c| {
368                        c.keep_fnames = true;
369                        c
370                    })
371                    .map(BoolOrDataConfig::from_obj)
372                    .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
373                let mangle = c
374                    .mangle
375                    .unwrap_as_option(|default| match default {
376                        Some(true) => Some(Default::default()),
377                        _ => None,
378                    })
379                    .map(|mut c| {
380                        c.keep_fn_names = true;
381                        c
382                    })
383                    .map(BoolOrDataConfig::from_obj)
384                    .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
385                JsMinifyOptions {
386                    compress,
387                    mangle,
388                    ..c
389                }
390            });
391        }
392
393        if js_minify.is_some() && js_minify.as_ref().unwrap().keep_classnames {
394            js_minify = js_minify.map(|c| {
395                let compress = c
396                    .compress
397                    .unwrap_as_option(|default| match default {
398                        Some(true) => Some(Default::default()),
399                        _ => None,
400                    })
401                    .map(|mut c| {
402                        c.keep_classnames = true;
403                        c
404                    })
405                    .map(BoolOrDataConfig::from_obj)
406                    .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
407                let mangle = c
408                    .mangle
409                    .unwrap_as_option(|default| match default {
410                        Some(true) => Some(Default::default()),
411                        _ => None,
412                    })
413                    .map(|mut c| {
414                        c.keep_class_names = true;
415                        c
416                    })
417                    .map(BoolOrDataConfig::from_obj)
418                    .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
419                JsMinifyOptions {
420                    compress,
421                    mangle,
422                    ..c
423                }
424            });
425        }
426
427        let regenerator = transform.regenerator.clone();
428
429        let preserve_comments = if preserve_all_comments {
430            BoolOr::Bool(true)
431        } else {
432            js_minify
433                .as_ref()
434                .map(|v| match v.format.comments.clone().into_inner() {
435                    Some(v) => v,
436                    None => BoolOr::Bool(true),
437                })
438                .unwrap_or_else(|| {
439                    BoolOr::Data(if cfg.minify.into_bool() {
440                        JsMinifyCommentOption::PreserveSomeComments
441                    } else {
442                        JsMinifyCommentOption::PreserveAllComments
443                    })
444                })
445        };
446
447        if syntax.typescript() {
448            transform.legacy_decorator = true.into();
449        }
450        let optimizer = transform.optimizer;
451
452        let const_modules = {
453            let enabled = transform.const_modules.is_some();
454            let config = transform.const_modules.unwrap_or_default();
455
456            let globals = config.globals;
457            Optional::new(const_modules(cm.clone(), globals), enabled)
458        };
459
460        let json_parse_pass = {
461            optimizer
462                .as_ref()
463                .and_then(|v| v.jsonify)
464                .as_ref()
465                .map(|cfg| json_parse(cfg.min_cost))
466        };
467
468        let simplifier_pass = {
469            if let Some(ref opts) = optimizer.as_ref().and_then(|o| o.simplify) {
470                match opts {
471                    SimplifyOption::Bool(allow_simplify) => {
472                        if *allow_simplify {
473                            Some(simplifier(unresolved_mark, Default::default()))
474                        } else {
475                            None
476                        }
477                    }
478                    SimplifyOption::Json(cfg) => Some(simplifier(
479                        unresolved_mark,
480                        SimplifyConfig {
481                            dce: DceConfig {
482                                preserve_imports_with_side_effects: cfg
483                                    .preserve_imports_with_side_effects,
484                                ..Default::default()
485                            },
486                            ..Default::default()
487                        },
488                    )),
489                }
490            } else {
491                None
492            }
493        };
494
495        let optimization = {
496            optimizer
497                .and_then(|o| o.globals)
498                .map(|opts| opts.build(cm, handler))
499        };
500
501        let unresolved_ctxt = SyntaxContext::empty().apply_mark(unresolved_mark);
502        let top_level_ctxt = SyntaxContext::empty().apply_mark(top_level_mark);
503
504        let pass = (
505            const_modules,
506            optimization,
507            Optional::new(export_default_from(), syntax.export_default_from()),
508            simplifier_pass,
509            json_parse_pass,
510        );
511
512        let import_export_assign_config = match cfg.module {
513            Some(ModuleConfig::Es6(..)) => TsImportExportAssignConfig::EsNext,
514            Some(ModuleConfig::CommonJs(..))
515            | Some(ModuleConfig::Amd(..))
516            | Some(ModuleConfig::Umd(..)) => TsImportExportAssignConfig::Preserve,
517            Some(ModuleConfig::NodeNext(..)) => TsImportExportAssignConfig::NodeNext,
518            // TODO: should Preserve for SystemJS
519            _ => TsImportExportAssignConfig::Classic,
520        };
521
522        let verbatim_module_syntax = transform.verbatim_module_syntax.into_bool();
523
524        let charset = cfg.jsc.output.charset.or_else(|| {
525            if js_minify.as_ref()?.format.ascii_only {
526                Some(OutputCharset::Ascii)
527            } else {
528                None
529            }
530        });
531
532        // inline_script defaults to true, but it's case only if minify is enabled.
533        // This is because minifier API is compatible with Terser, and Terser
534        // defaults to true, while by default swc itself doesn't enable
535        // inline_script by default.
536        let codegen_inline_script = js_minify.as_ref().map_or(false, |v| v.format.inline_script);
537
538        let preamble = if !cfg.jsc.output.preamble.is_empty() {
539            cfg.jsc.output.preamble
540        } else {
541            js_minify
542                .as_ref()
543                .map(|v| v.format.preamble.clone())
544                .unwrap_or_default()
545        };
546
547        let paths = paths.into_iter().collect();
548        let resolver = ModuleConfig::get_resolver(&base_url, paths, base, cfg.module.as_ref());
549
550        let pass = PassBuilder::new(
551            cm,
552            handler,
553            loose,
554            assumptions,
555            top_level_mark,
556            unresolved_mark,
557            pass,
558        )
559        .target(es_version)
560        .skip_helper_injection(self.skip_helper_injection)
561        .minify(js_minify)
562        .hygiene(if self.disable_hygiene {
563            None
564        } else {
565            Some(hygiene::Config {
566                keep_class_names,
567                ..Default::default()
568            })
569        })
570        .fixer(!self.disable_fixer)
571        .preset_env(cfg.env)
572        .regenerator(regenerator)
573        .finalize(
574            syntax,
575            cfg.module,
576            comments.map(|v| v as _),
577            resolver.clone(),
578        );
579
580        let keep_import_attributes = experimental.keep_import_attributes.into_bool();
581        let disable_all_lints = experimental.disable_all_lints.into_bool();
582
583        #[cfg(feature = "plugin")]
584        let plugin_transforms: Box<dyn Pass> = {
585            let transform_filename = match base {
586                FileName::Real(path) => path.as_os_str().to_str().map(String::from),
587                FileName::Custom(filename) => Some(filename.to_owned()),
588                _ => None,
589            };
590            let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
591                transform_filename,
592                self.env_name.to_owned(),
593                None,
594            ));
595
596            // Embedded runtime plugin target, based on assumption we have
597            // 1. filesystem access for the cache
598            // 2. embedded runtime can compiles & execute wasm
599            #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
600            {
601                use swc_ecma_loader::resolve::Resolve;
602
603                let plugin_resolver = CachingResolver::new(
604                    40,
605                    NodeModulesResolver::new(
606                        swc_ecma_loader::TargetEnv::Node,
607                        Default::default(),
608                        true,
609                    ),
610                );
611
612                if let Some(plugins) = &experimental.plugins {
613                    // Currently swc enables filesystemcache by default on Embedded runtime plugin
614                    // target.
615                    init_plugin_module_cache_once(true, &experimental.cache_root);
616
617                    let mut inner_cache = PLUGIN_MODULE_CACHE
618                        .inner
619                        .get()
620                        .expect("Cache should be available")
621                        .lock();
622
623                    // Populate cache to the plugin modules if not loaded
624                    for plugin_config in plugins.iter() {
625                        let plugin_name = &plugin_config.0;
626
627                        if !inner_cache.contains(&plugin_name) {
628                            let resolved_path = plugin_resolver.resolve(
629                                &FileName::Real(PathBuf::from(&plugin_name)),
630                                &plugin_name,
631                            )?;
632
633                            let path = if let FileName::Real(value) = resolved_path.filename {
634                                value
635                            } else {
636                                anyhow::bail!("Failed to resolve plugin path: {:?}", resolved_path);
637                            };
638
639                            inner_cache.store_bytes_from_path(&path, &plugin_name)?;
640                            tracing::debug!("Initialized WASM plugin {plugin_name}");
641                        }
642                    }
643                }
644
645                Box::new(crate::plugin::plugins(
646                    experimental.plugins,
647                    transform_metadata_context,
648                    comments.cloned(),
649                    cm.clone(),
650                    unresolved_mark,
651                ))
652            }
653
654            // Native runtime plugin target, based on assumption we have
655            // 1. no filesystem access, loading binary / cache management should be
656            // performed externally
657            // 2. native runtime compiles & execute wasm (i.e v8 on node, chrome)
658            #[cfg(all(feature = "plugin", target_arch = "wasm32"))]
659            {
660                handler.warn(
661                    "Currently @swc/wasm does not support plugins, plugin transform will be \
662                     skipped. Refer https://github.com/swc-project/swc/issues/3934 for the details.",
663                );
664
665                Box::new(noop())
666            }
667        };
668
669        #[cfg(not(feature = "plugin"))]
670        let plugin_transforms: Box<dyn Pass> = {
671            if experimental.plugins.is_some() {
672                handler.warn(
673                    "Plugin is not supported with current @swc/core. Plugin transform will be \
674                     skipped.",
675                );
676            }
677            Box::new(noop_pass())
678        };
679
680        let mut plugin_transforms = Some(plugin_transforms);
681
682        let pass: Box<dyn Pass> = if experimental
683            .disable_builtin_transforms_for_internal_testing
684            .into_bool()
685        {
686            plugin_transforms.unwrap()
687        } else {
688            let decorator_pass: Box<dyn Pass> =
689                match transform.decorator_version.unwrap_or_default() {
690                    DecoratorVersion::V202112 => Box::new(decorators(decorators::Config {
691                        legacy: transform.legacy_decorator.into_bool(),
692                        emit_metadata: transform.decorator_metadata.into_bool(),
693                        use_define_for_class_fields: !assumptions.set_public_class_fields,
694                    })),
695                    DecoratorVersion::V202203 => Box::new(
696                        swc_ecma_transforms::proposals::decorator_2022_03::decorator_2022_03(),
697                    ),
698                    DecoratorVersion::V202311 => todo!("2023-11 decorator"),
699                };
700
701            Box::new((
702                (
703                    if experimental.run_plugin_first.into_bool() {
704                        plugin_transforms.take()
705                    } else {
706                        None
707                    },
708                    Optional::new(
709                        lint_pass(swc_ecma_lints::rules::all(LintParams {
710                            program: &program,
711                            lint_config: &lints,
712                            top_level_ctxt,
713                            unresolved_ctxt,
714                            es_version,
715                            source_map: cm.clone(),
716                        })),
717                        !disable_all_lints,
718                    ),
719                    // Decorators may use type information
720                    Optional::new(decorator_pass, syntax.decorators()),
721                    Optional::new(
722                        explicit_resource_management(),
723                        syntax.explicit_resource_management(),
724                    ),
725                ),
726                // The transform strips import assertions, so it's only enabled if
727                // keep_import_assertions is false.
728                (
729                    Optional::new(import_attributes(), !keep_import_attributes),
730                    Optional::new(
731                        typescript::tsx::<Option<&dyn Comments>>(
732                            cm.clone(),
733                            typescript::Config {
734                                import_export_assign_config,
735                                verbatim_module_syntax,
736                                ..Default::default()
737                            },
738                            typescript::TsxConfig {
739                                pragma: Some(
740                                    transform
741                                        .react
742                                        .pragma
743                                        .clone()
744                                        .unwrap_or_else(default_pragma),
745                                ),
746                                pragma_frag: Some(
747                                    transform
748                                        .react
749                                        .pragma_frag
750                                        .clone()
751                                        .unwrap_or_else(default_pragma_frag),
752                                ),
753                            },
754                            comments.map(|v| v as _),
755                            unresolved_mark,
756                            top_level_mark,
757                        ),
758                        syntax.typescript(),
759                    ),
760                ),
761                (
762                    plugin_transforms.take(),
763                    custom_before_pass(&program),
764                    // handle jsx
765                    Optional::new(
766                        react::react::<&dyn Comments>(
767                            cm.clone(),
768                            comments.map(|v| v as _),
769                            transform.react,
770                            top_level_mark,
771                            unresolved_mark,
772                        ),
773                        syntax.jsx(),
774                    ),
775                    pass,
776                    Optional::new(jest::jest(), transform.hidden.jest.into_bool()),
777                    Optional::new(
778                        dropped_comments_preserver(comments.cloned()),
779                        preserve_all_comments,
780                    ),
781                ),
782            ))
783        };
784
785        Ok(BuiltInput {
786            program,
787            minify: cfg.minify.into_bool(),
788            pass,
789            external_helpers,
790            syntax,
791            target: es_version,
792            is_module,
793            source_maps: source_maps.unwrap_or(SourceMapsConfig::Bool(false)),
794            inline_sources_content: cfg.inline_sources_content.into_bool(),
795            input_source_map: cfg.input_source_map.clone().unwrap_or_default(),
796            output_path: output_path.map(|v| v.to_path_buf()),
797            source_root,
798            source_file_name,
799            comments: comments.cloned(),
800            preserve_comments,
801            emit_source_map_columns: cfg.emit_source_map_columns.into_bool(),
802            output: JscOutputConfig {
803                charset,
804                preamble,
805                preserve_annotations: cfg.jsc.output.preserve_annotations,
806            },
807            emit_assert_for_import_attributes: experimental
808                .emit_assert_for_import_attributes
809                .into_bool(),
810            codegen_inline_script,
811            emit_isolated_dts: experimental.emit_isolated_dts.into_bool(),
812            unresolved_mark,
813            resolver,
814        })
815    }
816}
817
818#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
819pub enum RootMode {
820    #[default]
821    #[serde(rename = "root")]
822    Root,
823    #[serde(rename = "upward")]
824    Upward,
825    #[serde(rename = "upward-optional")]
826    UpwardOptional,
827}
828
829const fn default_swcrc() -> bool {
830    true
831}
832
833#[derive(Debug, Clone, Serialize, Deserialize)]
834#[serde(untagged)]
835pub enum ConfigFile {
836    Bool(bool),
837    Str(String),
838}
839
840impl Default for ConfigFile {
841    fn default() -> Self {
842        ConfigFile::Bool(true)
843    }
844}
845
846#[derive(Debug, Default, Clone, Serialize, Deserialize)]
847#[serde(rename_all = "camelCase")]
848pub struct CallerOptions {
849    pub name: String,
850}
851
852#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
853fn default_cwd() -> PathBuf {
854    static CWD: Lazy<PathBuf> = Lazy::new(|| ::std::env::current_dir().unwrap());
855
856    CWD.clone()
857}
858
859/// `.swcrc` file
860#[derive(Debug, Clone, Deserialize)]
861#[serde(untagged, rename = "swcrc")]
862#[allow(clippy::large_enum_variant)]
863pub enum Rc {
864    Single(Config),
865    Multi(Vec<Config>),
866}
867
868impl Default for Rc {
869    fn default() -> Self {
870        Rc::Multi(vec![
871            Config {
872                env: None,
873                test: None,
874                exclude: Some(FileMatcher::Regex("\\.tsx?$".into())),
875                jsc: JscConfig {
876                    syntax: Some(Default::default()),
877                    ..Default::default()
878                },
879                ..Default::default()
880            },
881            Config {
882                env: None,
883                test: Some(FileMatcher::Regex("\\.tsx$".into())),
884                exclude: None,
885                jsc: JscConfig {
886                    syntax: Some(Syntax::Typescript(TsSyntax {
887                        tsx: true,
888                        ..Default::default()
889                    })),
890                    ..Default::default()
891                },
892                ..Default::default()
893            },
894            Config {
895                env: None,
896                test: Some(FileMatcher::Regex("\\.(cts|mts)$".into())),
897                exclude: None,
898                jsc: JscConfig {
899                    syntax: Some(Syntax::Typescript(TsSyntax {
900                        tsx: false,
901                        disallow_ambiguous_jsx_like: true,
902                        ..Default::default()
903                    })),
904                    ..Default::default()
905                },
906                ..Default::default()
907            },
908            Config {
909                env: None,
910                test: Some(FileMatcher::Regex("\\.ts$".into())),
911                exclude: None,
912                jsc: JscConfig {
913                    syntax: Some(Syntax::Typescript(TsSyntax {
914                        tsx: false,
915                        ..Default::default()
916                    })),
917                    ..Default::default()
918                },
919                ..Default::default()
920            },
921        ])
922    }
923}
924
925impl Rc {
926    /// This method returns `Ok(None)` if the file should be ignored.
927    pub fn into_config(self, filename: Option<&Path>) -> Result<Option<Config>, Error> {
928        let cs = match self {
929            Rc::Single(mut c) => match filename {
930                Some(filename) => {
931                    if c.matches(filename)? {
932                        c.adjust(filename);
933
934                        return Ok(Some(c));
935                    } else {
936                        return Ok(None);
937                    }
938                }
939                // TODO
940                None => return Ok(Some(c)),
941            },
942            Rc::Multi(cs) => cs,
943        };
944
945        match filename {
946            Some(filename) => {
947                for mut c in cs {
948                    if c.matches(filename)? {
949                        c.adjust(filename);
950
951                        return Ok(Some(c));
952                    }
953                }
954            }
955            None => return Ok(Some(Config::default())),
956        }
957
958        bail!(".swcrc exists but not matched")
959    }
960}
961
962/// A single object in the `.swcrc` file
963#[derive(Debug, Default, Clone, Deserialize, Merge)]
964#[serde(deny_unknown_fields, rename_all = "camelCase")]
965pub struct Config {
966    #[serde(default)]
967    pub env: Option<swc_ecma_preset_env::Config>,
968
969    #[serde(default)]
970    pub test: Option<FileMatcher>,
971
972    #[serde(default)]
973    pub exclude: Option<FileMatcher>,
974
975    #[serde(default)]
976    pub jsc: JscConfig,
977
978    #[serde(default)]
979    pub module: Option<ModuleConfig>,
980
981    #[serde(default)]
982    pub minify: BoolConfig<false>,
983
984    #[serde(default)]
985    pub input_source_map: Option<InputSourceMap>,
986
987    /// Possible values are: `'inline'`, `true`, `false`.
988    #[serde(default)]
989    pub source_maps: Option<SourceMapsConfig>,
990
991    #[serde(default)]
992    pub inline_sources_content: BoolConfig<true>,
993
994    #[serde(default)]
995    pub emit_source_map_columns: BoolConfig<true>,
996
997    #[serde(default)]
998    pub error: ErrorConfig,
999
1000    #[serde(default)]
1001    pub is_module: Option<IsModule>,
1002
1003    #[serde(rename = "$schema")]
1004    pub schema: Option<String>,
1005}
1006
1007impl Config {
1008    /// Adjust config for `file`.
1009    ///
1010    ///
1011    ///
1012    /// - typescript: `tsx` will be modified if file extension is `ts`.
1013    pub fn adjust(&mut self, file: &Path) {
1014        if let Some(Syntax::Typescript(TsSyntax { tsx, dts, .. })) = &mut self.jsc.syntax {
1015            let is_dts = file
1016                .file_name()
1017                .and_then(|f| f.to_str())
1018                .map(|s| s.ends_with(".d.ts"))
1019                .unwrap_or(false);
1020
1021            if is_dts {
1022                *dts = true;
1023            }
1024
1025            if file.extension() == Some("tsx".as_ref()) {
1026                *tsx = true;
1027            } else if file.extension() == Some("ts".as_ref()) {
1028                *tsx = false;
1029            }
1030        }
1031    }
1032}
1033
1034#[derive(Debug, Clone, Serialize, Deserialize)]
1035#[serde(untagged)]
1036pub enum FileMatcher {
1037    None,
1038    Regex(CachedRegex),
1039    Multi(Vec<FileMatcher>),
1040}
1041
1042impl Default for FileMatcher {
1043    fn default() -> Self {
1044        Self::None
1045    }
1046}
1047
1048impl FileMatcher {
1049    pub fn matches(&self, filename: &Path) -> Result<bool, Error> {
1050        match self {
1051            FileMatcher::None => Ok(false),
1052
1053            FileMatcher::Regex(re) => {
1054                let filename = if cfg!(target_os = "windows") {
1055                    filename.to_string_lossy().replace('\\', "/")
1056                } else {
1057                    filename.to_string_lossy().to_string()
1058                };
1059
1060                Ok(re.is_match(&filename))
1061            }
1062            FileMatcher::Multi(ref v) => {
1063                //
1064                for m in v {
1065                    if m.matches(filename)? {
1066                        return Ok(true);
1067                    }
1068                }
1069
1070                Ok(false)
1071            }
1072        }
1073    }
1074}
1075
1076impl Config {
1077    pub fn matches(&self, filename: &Path) -> Result<bool, Error> {
1078        if let Some(ref exclude) = self.exclude {
1079            if exclude.matches(filename)? {
1080                return Ok(false);
1081            }
1082        }
1083
1084        if let Some(ref include) = self.test {
1085            if include.matches(filename)? {
1086                return Ok(true);
1087            }
1088            return Ok(false);
1089        }
1090
1091        Ok(true)
1092    }
1093}
1094
1095/// One `BuiltConfig` per a directory with swcrc
1096#[non_exhaustive]
1097pub struct BuiltInput<P: Pass> {
1098    pub program: Program,
1099    pub pass: P,
1100    pub syntax: Syntax,
1101    pub target: EsVersion,
1102    /// Minification for **codegen**. Minifier transforms will be inserted into
1103    /// `pass`.
1104    pub minify: bool,
1105    pub external_helpers: bool,
1106    pub source_maps: SourceMapsConfig,
1107    pub input_source_map: InputSourceMap,
1108    pub is_module: IsModule,
1109    pub output_path: Option<PathBuf>,
1110
1111    pub source_root: Option<String>,
1112    pub source_file_name: Option<String>,
1113
1114    pub comments: Option<SingleThreadedComments>,
1115    pub preserve_comments: BoolOr<JsMinifyCommentOption>,
1116
1117    pub inline_sources_content: bool,
1118    pub emit_source_map_columns: bool,
1119
1120    pub output: JscOutputConfig,
1121    pub emit_assert_for_import_attributes: bool,
1122    pub codegen_inline_script: bool,
1123
1124    pub emit_isolated_dts: bool,
1125    pub unresolved_mark: Mark,
1126    pub resolver: Option<(FileName, Arc<dyn ImportResolver>)>,
1127}
1128
1129impl<P> BuiltInput<P>
1130where
1131    P: Pass,
1132{
1133    pub fn with_pass<N>(self, map: impl FnOnce(P) -> N) -> BuiltInput<N>
1134    where
1135        N: Pass,
1136    {
1137        BuiltInput {
1138            program: self.program,
1139            pass: map(self.pass),
1140            syntax: self.syntax,
1141            target: self.target,
1142            minify: self.minify,
1143            external_helpers: self.external_helpers,
1144            source_maps: self.source_maps,
1145            input_source_map: self.input_source_map,
1146            is_module: self.is_module,
1147            output_path: self.output_path,
1148            source_root: self.source_root,
1149            source_file_name: self.source_file_name,
1150            comments: self.comments,
1151            preserve_comments: self.preserve_comments,
1152            inline_sources_content: self.inline_sources_content,
1153            emit_source_map_columns: self.emit_source_map_columns,
1154            output: self.output,
1155            emit_assert_for_import_attributes: self.emit_assert_for_import_attributes,
1156            codegen_inline_script: self.codegen_inline_script,
1157            emit_isolated_dts: self.emit_isolated_dts,
1158            unresolved_mark: self.unresolved_mark,
1159            resolver: self.resolver,
1160        }
1161    }
1162}
1163
1164/// `jsc` in  `.swcrc`.
1165#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1166#[serde(deny_unknown_fields, rename_all = "camelCase")]
1167pub struct JscConfig {
1168    #[serde(default)]
1169    pub assumptions: Option<Assumptions>,
1170
1171    #[serde(rename = "parser", default)]
1172    pub syntax: Option<Syntax>,
1173
1174    #[serde(default)]
1175    pub transform: MergingOption<TransformConfig>,
1176
1177    #[serde(default)]
1178    pub external_helpers: BoolConfig<false>,
1179
1180    #[serde(default)]
1181    pub target: Option<EsVersion>,
1182
1183    #[serde(default)]
1184    pub loose: BoolConfig<false>,
1185
1186    #[serde(default)]
1187    pub keep_class_names: BoolConfig<false>,
1188
1189    #[serde(default)]
1190    pub base_url: PathBuf,
1191
1192    #[serde(default)]
1193    pub paths: Paths,
1194
1195    #[serde(default)]
1196    pub minify: Option<JsMinifyOptions>,
1197
1198    #[serde(default)]
1199    pub experimental: JscExperimental,
1200
1201    #[serde(default)]
1202    pub lints: LintConfig,
1203
1204    #[serde(default)]
1205    pub preserve_all_comments: BoolConfig<false>,
1206
1207    #[serde(default)]
1208    pub output: JscOutputConfig,
1209}
1210
1211#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1212#[serde(deny_unknown_fields, rename_all = "camelCase")]
1213pub struct JscOutputConfig {
1214    #[serde(default)]
1215    pub charset: Option<OutputCharset>,
1216
1217    #[serde(default)]
1218    pub preamble: String,
1219
1220    #[serde(default)]
1221    pub preserve_annotations: BoolConfig<false>,
1222}
1223
1224#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1225#[serde(deny_unknown_fields, rename_all = "camelCase")]
1226pub enum OutputCharset {
1227    #[default]
1228    #[serde(rename = "utf8")]
1229    Utf8,
1230    #[serde(rename = "ascii")]
1231    Ascii,
1232}
1233
1234/// `jsc.experimental` in `.swcrc`
1235#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1236#[serde(deny_unknown_fields, rename_all = "camelCase")]
1237pub struct JscExperimental {
1238    /// This requires cargo feature `plugin`.
1239    #[serde(default)]
1240    pub plugins: Option<Vec<PluginConfig>>,
1241    /// If true, keeps import assertions in the output.
1242    #[serde(default, alias = "keepImportAssertions")]
1243    pub keep_import_attributes: BoolConfig<false>,
1244
1245    #[serde(default)]
1246    pub emit_assert_for_import_attributes: BoolConfig<false>,
1247    /// Location where swc may stores its intermediate cache.
1248    /// Currently this is only being used for wasm plugin's bytecache.
1249    /// Path should be absolute directory, which will be created if not exist.
1250    /// This configuration behavior can change anytime under experimental flag
1251    /// and will not be considered as breaking changes.
1252    #[serde(default)]
1253    pub cache_root: Option<String>,
1254
1255    #[serde(default)]
1256    pub run_plugin_first: BoolConfig<false>,
1257
1258    #[serde(default)]
1259    pub disable_builtin_transforms_for_internal_testing: BoolConfig<false>,
1260
1261    /// Emit TypeScript definitions for `.ts`, `.tsx` files.
1262    ///
1263    /// This requires `isolatedDeclartion` feature of TypeScript 5.5.
1264    #[serde(default)]
1265    pub emit_isolated_dts: BoolConfig<false>,
1266
1267    #[serde(default)]
1268    pub disable_all_lints: BoolConfig<true>,
1269}
1270
1271#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1272pub enum ErrorFormat {
1273    #[serde(rename = "json")]
1274    Json,
1275    #[serde(rename = "normal")]
1276    Normal,
1277}
1278
1279impl ErrorFormat {
1280    pub fn format(&self, err: &Error) -> String {
1281        match self {
1282            ErrorFormat::Normal => format!("{:?}", err),
1283            ErrorFormat::Json => {
1284                let mut map = serde_json::Map::new();
1285
1286                map.insert("message".into(), serde_json::Value::String(err.to_string()));
1287
1288                map.insert(
1289                    "stack".into(),
1290                    serde_json::Value::Array(
1291                        err.chain()
1292                            .skip(1)
1293                            .map(|err| err.to_string())
1294                            .map(serde_json::Value::String)
1295                            .collect(),
1296                    ),
1297                );
1298
1299                serde_json::to_string(&map).unwrap()
1300            }
1301        }
1302    }
1303}
1304
1305impl Default for ErrorFormat {
1306    fn default() -> Self {
1307        Self::Normal
1308    }
1309}
1310
1311/// `paths` section of `tsconfig.json`.
1312pub type Paths = IndexMap<String, Vec<String>, FxBuildHasher>;
1313pub(crate) type CompiledPaths = Vec<(String, Vec<String>)>;
1314
1315#[derive(Debug, Clone, Serialize, Deserialize)]
1316#[serde(deny_unknown_fields, rename_all = "camelCase")]
1317#[serde(tag = "type")]
1318pub enum ModuleConfig {
1319    #[serde(rename = "commonjs")]
1320    CommonJs(modules::common_js::Config),
1321    #[serde(rename = "umd")]
1322    Umd(modules::umd::Config),
1323    #[serde(rename = "amd")]
1324    Amd(modules::amd::Config),
1325    #[serde(rename = "systemjs")]
1326    SystemJs(modules::system_js::Config),
1327    #[serde(rename = "es6")]
1328    Es6(EsModuleConfig),
1329    #[serde(rename = "nodenext")]
1330    NodeNext(EsModuleConfig),
1331}
1332
1333impl ModuleConfig {
1334    pub fn build<'cmt>(
1335        cm: Arc<SourceMap>,
1336        comments: Option<&'cmt dyn Comments>,
1337        config: Option<ModuleConfig>,
1338        unresolved_mark: Mark,
1339        available_features: FeatureFlag,
1340        resolver: Option<(FileName, Arc<dyn ImportResolver>)>,
1341    ) -> Box<dyn Pass + 'cmt> {
1342        let resolver = if let Some((base, resolver)) = resolver {
1343            Resolver::Real { base, resolver }
1344        } else {
1345            Resolver::Default
1346        };
1347
1348        match config {
1349            None | Some(ModuleConfig::Es6(..)) | Some(ModuleConfig::NodeNext(..)) => match resolver
1350            {
1351                Resolver::Default => Box::new(noop_pass()),
1352                Resolver::Real { base, resolver } => Box::new(import_rewriter(base, resolver)),
1353            },
1354            Some(ModuleConfig::CommonJs(config)) => Box::new(modules::common_js::common_js(
1355                resolver,
1356                unresolved_mark,
1357                config,
1358                available_features,
1359            )),
1360            Some(ModuleConfig::Umd(config)) => Box::new(modules::umd::umd(
1361                cm,
1362                resolver,
1363                unresolved_mark,
1364                config,
1365                available_features,
1366            )),
1367            Some(ModuleConfig::Amd(config)) => Box::new(modules::amd::amd(
1368                resolver,
1369                unresolved_mark,
1370                config,
1371                available_features,
1372                comments,
1373            )),
1374            Some(ModuleConfig::SystemJs(config)) => Box::new(modules::system_js::system_js(
1375                resolver,
1376                unresolved_mark,
1377                config,
1378            )),
1379        }
1380    }
1381
1382    pub fn get_resolver(
1383        base_url: &Path,
1384        paths: CompiledPaths,
1385        base: &FileName,
1386        config: Option<&ModuleConfig>,
1387    ) -> Option<(FileName, Arc<dyn ImportResolver>)> {
1388        let skip_resolver = base_url.as_os_str().is_empty() && paths.is_empty();
1389
1390        if skip_resolver {
1391            return None;
1392        }
1393
1394        let base = match base {
1395            FileName::Real(v) if !skip_resolver => {
1396                FileName::Real(v.canonicalize().unwrap_or_else(|_| v.to_path_buf()))
1397            }
1398            _ => base.clone(),
1399        };
1400
1401        let base_url = base_url.to_path_buf();
1402        let resolver = match config {
1403            None => build_resolver(base_url, paths, false, &util::Config::default_js_ext()),
1404            Some(ModuleConfig::Es6(config)) | Some(ModuleConfig::NodeNext(config)) => {
1405                build_resolver(
1406                    base_url,
1407                    paths,
1408                    config.config.resolve_fully,
1409                    &config.config.out_file_extension,
1410                )
1411            }
1412            Some(ModuleConfig::CommonJs(config)) => build_resolver(
1413                base_url,
1414                paths,
1415                config.resolve_fully,
1416                &config.out_file_extension,
1417            ),
1418            Some(ModuleConfig::Umd(config)) => build_resolver(
1419                base_url,
1420                paths,
1421                config.config.resolve_fully,
1422                &config.config.out_file_extension,
1423            ),
1424            Some(ModuleConfig::Amd(config)) => build_resolver(
1425                base_url,
1426                paths,
1427                config.config.resolve_fully,
1428                &config.config.out_file_extension,
1429            ),
1430            Some(ModuleConfig::SystemJs(config)) => build_resolver(
1431                base_url,
1432                paths,
1433                config.config.resolve_fully,
1434                &config.config.out_file_extension,
1435            ),
1436        };
1437
1438        Some((base, resolver))
1439    }
1440}
1441
1442#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1443#[serde(deny_unknown_fields, rename_all = "camelCase")]
1444pub struct TransformConfig {
1445    #[serde(default)]
1446    pub react: react::Options,
1447
1448    #[serde(default)]
1449    pub const_modules: Option<ConstModulesConfig>,
1450
1451    #[serde(default)]
1452    pub optimizer: Option<OptimizerConfig>,
1453
1454    #[serde(default)]
1455    pub legacy_decorator: BoolConfig<false>,
1456
1457    #[serde(default)]
1458    pub decorator_metadata: BoolConfig<false>,
1459
1460    #[serde(default)]
1461    pub hidden: HiddenTransformConfig,
1462
1463    #[serde(default)]
1464    pub regenerator: regenerator::Config,
1465
1466    #[serde(default)]
1467    #[deprecated]
1468    pub treat_const_enum_as_enum: BoolConfig<false>,
1469
1470    /// https://www.typescriptlang.org/tsconfig#useDefineForClassFields
1471    #[serde(default)]
1472    pub use_define_for_class_fields: BoolConfig<true>,
1473
1474    /// https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax
1475    #[serde(default)]
1476    pub verbatim_module_syntax: BoolConfig<false>,
1477
1478    #[serde(default)]
1479    pub decorator_version: Option<DecoratorVersion>,
1480}
1481
1482#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1483#[serde(deny_unknown_fields, rename_all = "camelCase")]
1484pub struct HiddenTransformConfig {
1485    #[serde(default)]
1486    pub jest: BoolConfig<false>,
1487}
1488
1489#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1490#[serde(deny_unknown_fields, rename_all = "camelCase")]
1491pub struct ConstModulesConfig {
1492    #[serde(default)]
1493    pub globals: FxHashMap<Atom, FxHashMap<Atom, String>>,
1494}
1495
1496#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1497#[serde(deny_unknown_fields, rename_all = "camelCase")]
1498pub struct OptimizerConfig {
1499    #[serde(default)]
1500    pub globals: Option<GlobalPassOption>,
1501
1502    #[serde(default)]
1503    pub simplify: Option<SimplifyOption>,
1504
1505    #[serde(default)]
1506    pub jsonify: Option<JsonifyOption>,
1507}
1508
1509#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
1510#[serde(untagged)]
1511pub enum SimplifyOption {
1512    Bool(bool),
1513    Json(SimplifyJsonOption),
1514}
1515
1516impl Default for SimplifyOption {
1517    fn default() -> Self {
1518        SimplifyOption::Bool(true)
1519    }
1520}
1521
1522#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
1523#[serde(deny_unknown_fields, rename_all = "camelCase")]
1524pub struct SimplifyJsonOption {
1525    #[serde(default = "default_preserve_imports_with_side_effects")]
1526    pub preserve_imports_with_side_effects: bool,
1527}
1528
1529fn default_preserve_imports_with_side_effects() -> bool {
1530    true
1531}
1532
1533#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
1534#[serde(deny_unknown_fields, rename_all = "camelCase")]
1535pub struct JsonifyOption {
1536    #[serde(default = "default_jsonify_min_cost")]
1537    pub min_cost: usize,
1538}
1539
1540fn default_jsonify_min_cost() -> usize {
1541    1024
1542}
1543
1544#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Merge)]
1545#[serde(deny_unknown_fields, rename_all = "camelCase")]
1546pub struct ErrorConfig {
1547    pub filename: BoolConfig<true>,
1548}
1549
1550#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1551#[serde(deny_unknown_fields, rename_all = "camelCase")]
1552pub struct GlobalPassOption {
1553    #[serde(default)]
1554    pub vars: IndexMap<Atom, Atom, FxBuildHasher>,
1555    #[serde(default)]
1556    pub envs: GlobalInliningPassEnvs,
1557
1558    #[serde(default)]
1559    pub typeofs: FxHashMap<Atom, Atom>,
1560}
1561
1562#[derive(Debug, Clone, Serialize, Deserialize)]
1563#[serde(untagged)]
1564pub enum GlobalInliningPassEnvs {
1565    List(FxHashSet<String>),
1566    Map(FxHashMap<Atom, Atom>),
1567}
1568
1569impl Default for GlobalInliningPassEnvs {
1570    fn default() -> Self {
1571        let mut v = HashSet::default();
1572        v.insert(String::from("NODE_ENV"));
1573        v.insert(String::from("SWC_ENV"));
1574
1575        GlobalInliningPassEnvs::List(v)
1576    }
1577}
1578
1579impl GlobalPassOption {
1580    pub fn build(self, cm: &SourceMap, handler: &Handler) -> impl 'static + Pass {
1581        type ValuesMap = Arc<FxHashMap<Atom, Expr>>;
1582
1583        fn expr(cm: &SourceMap, handler: &Handler, src: String) -> Box<Expr> {
1584            let fm = cm.new_source_file(FileName::Anon.into(), src);
1585
1586            let mut errors = Vec::new();
1587            let expr = parse_file_as_expr(
1588                &fm,
1589                Syntax::Es(Default::default()),
1590                Default::default(),
1591                None,
1592                &mut errors,
1593            );
1594
1595            for e in errors {
1596                e.into_diagnostic(handler).emit()
1597            }
1598
1599            match expr {
1600                Ok(v) => v,
1601                _ => panic!("{} is not a valid expression", fm.src),
1602            }
1603        }
1604
1605        fn mk_map(
1606            cm: &SourceMap,
1607            handler: &Handler,
1608            values: impl Iterator<Item = (Atom, Atom)>,
1609            is_env: bool,
1610        ) -> ValuesMap {
1611            let mut m = HashMap::default();
1612
1613            for (k, v) in values {
1614                let v = if is_env {
1615                    format!("'{}'", v)
1616                } else {
1617                    (*v).into()
1618                };
1619                let v_str = v.clone();
1620
1621                let e = expr(cm, handler, v_str);
1622
1623                m.insert((*k).into(), *e);
1624            }
1625
1626            Arc::new(m)
1627        }
1628
1629        let env_map = if cfg!(target_arch = "wasm32") {
1630            Arc::new(Default::default())
1631        } else {
1632            match &self.envs {
1633                GlobalInliningPassEnvs::List(env_list) => {
1634                    static CACHE: Lazy<DashMap<Vec<String>, ValuesMap, FxBuildHasher>> =
1635                        Lazy::new(Default::default);
1636
1637                    let cache_key = env_list.iter().cloned().collect::<Vec<_>>();
1638                    if let Some(v) = CACHE.get(&cache_key).as_deref().cloned() {
1639                        v
1640                    } else {
1641                        let map = mk_map(
1642                            cm,
1643                            handler,
1644                            env::vars()
1645                                .filter(|(k, _)| env_list.contains(k))
1646                                .map(|(k, v)| (k.into(), v.into())),
1647                            true,
1648                        );
1649                        CACHE.insert(cache_key, map.clone());
1650                        map
1651                    }
1652                }
1653
1654                GlobalInliningPassEnvs::Map(map) => {
1655                    static CACHE: Lazy<DashMap<Vec<(Atom, Atom)>, ValuesMap, FxBuildHasher>> =
1656                        Lazy::new(Default::default);
1657
1658                    let cache_key = self
1659                        .vars
1660                        .iter()
1661                        .map(|(k, v)| (k.clone(), v.clone()))
1662                        .collect::<Vec<_>>();
1663                    if let Some(v) = CACHE.get(&cache_key) {
1664                        (*v).clone()
1665                    } else {
1666                        let map = mk_map(
1667                            cm,
1668                            handler,
1669                            map.iter().map(|(k, v)| (k.clone(), v.clone())),
1670                            false,
1671                        );
1672                        CACHE.insert(cache_key, map.clone());
1673                        map
1674                    }
1675                }
1676            }
1677        };
1678
1679        let global_exprs = {
1680            static CACHE: Lazy<DashMap<Vec<(Atom, Atom)>, GlobalExprMap, FxBuildHasher>> =
1681                Lazy::new(Default::default);
1682
1683            let cache_key = self
1684                .vars
1685                .iter()
1686                .filter(|(k, _)| k.contains('.'))
1687                .map(|(k, v)| (k.clone(), v.clone()))
1688                .collect::<Vec<_>>();
1689
1690            if let Some(v) = CACHE.get(&cache_key) {
1691                (*v).clone()
1692            } else {
1693                let map = self
1694                    .vars
1695                    .iter()
1696                    .filter(|(k, _)| k.contains('.'))
1697                    .map(|(k, v)| {
1698                        (
1699                            NodeIgnoringSpan::owned(*expr(cm, handler, k.to_string())),
1700                            *expr(cm, handler, v.to_string()),
1701                        )
1702                    })
1703                    .collect::<FxHashMap<_, _>>();
1704                let map = Arc::new(map);
1705                CACHE.insert(cache_key, map.clone());
1706                map
1707            }
1708        };
1709
1710        let global_map = {
1711            static CACHE: Lazy<DashMap<Vec<(Atom, Atom)>, ValuesMap, FxBuildHasher>> =
1712                Lazy::new(Default::default);
1713
1714            let cache_key = self
1715                .vars
1716                .iter()
1717                .filter(|(k, _)| !k.contains('.'))
1718                .map(|(k, v)| (k.clone(), v.clone()))
1719                .collect::<Vec<_>>();
1720            if let Some(v) = CACHE.get(&cache_key) {
1721                (*v).clone()
1722            } else {
1723                let map = mk_map(
1724                    cm,
1725                    handler,
1726                    self.vars.into_iter().filter(|(k, _)| !k.contains('.')),
1727                    false,
1728                );
1729                CACHE.insert(cache_key, map.clone());
1730                map
1731            }
1732        };
1733
1734        inline_globals2(env_map, global_map, global_exprs, Arc::new(self.typeofs))
1735    }
1736}
1737
1738fn default_env_name() -> String {
1739    if let Ok(v) = env::var("SWC_ENV") {
1740        return v;
1741    }
1742
1743    match env::var("NODE_ENV") {
1744        Ok(v) => v,
1745        Err(_) => "development".into(),
1746    }
1747}
1748
1749fn build_resolver(
1750    mut base_url: PathBuf,
1751    paths: CompiledPaths,
1752    resolve_fully: bool,
1753    file_extension: &str,
1754) -> SwcImportResolver {
1755    static CACHE: Lazy<DashMap<(PathBuf, CompiledPaths, bool), SwcImportResolver, FxBuildHasher>> =
1756        Lazy::new(Default::default);
1757
1758    // On Windows, we need to normalize path as UNC path.
1759    if cfg!(target_os = "windows") {
1760        base_url = base_url
1761            .canonicalize()
1762            .with_context(|| {
1763                format!(
1764                    "failed to canonicalize jsc.baseUrl(`{}`)\nThis is required on Windows \
1765                     because of UNC path.",
1766                    base_url.display()
1767                )
1768            })
1769            .unwrap();
1770    }
1771
1772    if let Some(cached) = CACHE.get(&(base_url.clone(), paths.clone(), resolve_fully)) {
1773        return cached.clone();
1774    }
1775
1776    let r = {
1777        let r = NodeModulesResolver::without_node_modules(
1778            swc_ecma_loader::TargetEnv::Node,
1779            Default::default(),
1780            true,
1781        );
1782
1783        let r = CachingResolver::new(1024, r);
1784
1785        let r = TsConfigResolver::new(r, base_url.clone(), paths.clone());
1786        let r = CachingResolver::new(256, r);
1787
1788        let r = NodeImportResolver::with_config(
1789            r,
1790            swc_ecma_transforms::modules::path::Config {
1791                base_dir: Some(base_url.clone()),
1792                resolve_fully,
1793                file_extension: file_extension.to_owned(),
1794            },
1795        );
1796        Arc::new(r)
1797    };
1798
1799    CACHE.insert((base_url, paths, resolve_fully), r.clone());
1800
1801    r
1802}