swc_ecma_transforms_module/
util.rs

1use is_macro::Is;
2use serde::{Deserialize, Serialize};
3use swc_atoms::Atom;
4use swc_common::DUMMY_SP;
5use swc_config::file_pattern::FilePattern;
6use swc_ecma_ast::*;
7use swc_ecma_utils::{
8    is_valid_prop_ident, member_expr, private_ident, quote_ident, quote_str, ExprFactory,
9    FunctionFactory, IsDirective,
10};
11
12use crate::{
13    module_decl_strip::{ExportItem, ExportKV},
14    SpanCtx,
15};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(deny_unknown_fields, rename_all = "camelCase")]
19pub struct Config {
20    #[serde(default)]
21    pub allow_top_level_this: bool,
22    #[serde(default)]
23    pub strict: bool,
24    #[serde(default = "default_strict_mode")]
25    pub strict_mode: bool,
26    #[serde(default)]
27    pub lazy: Lazy,
28    #[serde(default)]
29    pub import_interop: Option<ImportInterop>,
30    /// Emits `cjs-module-lexer` annotation
31    /// `cjs-module-lexer` is used in Node.js core for detecting the named
32    /// exports available when importing a CJS module into ESM.
33    /// swc will emit `cjs-module-lexer` detectable annotation with this option
34    /// enabled.
35    ///
36    /// Defaults to `true` if import_interop is Node, else `false`
37    #[serde(default)]
38    pub export_interop_annotation: Option<bool>,
39    #[serde(default)]
40    /// Note: deprecated
41    pub no_interop: bool,
42    #[serde(default)]
43    pub ignore_dynamic: bool,
44    #[serde(default)]
45    pub preserve_import_meta: bool,
46
47    #[serde(default)]
48    pub resolve_fully: bool,
49
50    #[serde(default = "Config::default_js_ext")]
51    pub out_file_extension: String,
52}
53
54impl Config {
55    pub fn default_js_ext() -> String {
56        "js".to_string()
57    }
58}
59
60impl Default for Config {
61    fn default() -> Self {
62        Config {
63            allow_top_level_this: false,
64            strict: false,
65            strict_mode: default_strict_mode(),
66            lazy: Lazy::default(),
67            import_interop: None,
68            export_interop_annotation: None,
69            no_interop: false,
70            ignore_dynamic: false,
71            preserve_import_meta: false,
72            resolve_fully: false,
73            out_file_extension: "js".to_string(),
74        }
75    }
76}
77
78const fn default_strict_mode() -> bool {
79    true
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Is, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub enum ImportInterop {
85    #[serde(alias = "babel")]
86    Swc,
87    Node,
88    None,
89}
90
91impl From<bool> for ImportInterop {
92    fn from(no_interop: bool) -> Self {
93        if no_interop {
94            ImportInterop::None
95        } else {
96            ImportInterop::Swc
97        }
98    }
99}
100
101impl Config {
102    #[inline(always)]
103    pub fn import_interop(&self) -> ImportInterop {
104        self.import_interop
105            .unwrap_or_else(|| self.no_interop.into())
106    }
107
108    #[inline(always)]
109    pub fn export_interop_annotation(&self) -> bool {
110        self.export_interop_annotation
111            .unwrap_or_else(|| self.import_interop == Some(ImportInterop::Node))
112    }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(deny_unknown_fields, rename_all = "camelCase")]
117pub struct LazyObjectConfig {
118    pub patterns: Vec<FilePattern>,
119}
120
121impl LazyObjectConfig {
122    pub fn is_lazy(&self, src: &Atom) -> bool {
123        self.patterns.iter().any(|pat| pat.is_match(src))
124    }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(untagged, deny_unknown_fields, rename_all = "camelCase")]
129pub enum Lazy {
130    Bool(bool),
131    List(Vec<Atom>),
132    Object(LazyObjectConfig),
133}
134
135impl Lazy {
136    pub fn is_lazy(&self, src: &Atom) -> bool {
137        match *self {
138            Lazy::Bool(false) => false,
139            Lazy::Bool(true) => !src.starts_with('.'),
140            Lazy::List(ref srcs) => srcs.contains(src),
141            Lazy::Object(ref object) => object.is_lazy(src),
142        }
143    }
144}
145
146impl Default for Lazy {
147    fn default() -> Self {
148        Lazy::Bool(false)
149    }
150}
151
152pub(super) fn local_name_for_src(src: &Atom) -> Atom {
153    let src = src.split('/').next_back().unwrap();
154    let src = src
155        .strip_suffix(".js")
156        .or_else(|| src.strip_suffix(".mjs"))
157        .unwrap_or(src);
158
159    let id = match Ident::verify_symbol(src) {
160        Ok(_) => src.into(),
161        Err(err) => err,
162    };
163
164    if !id.starts_with('_') {
165        format!("_{id}").into()
166    } else {
167        id.into()
168    }
169}
170
171/// Creates
172///
173///```js
174/// 
175///  Object.defineProperty(target, prop_name, {
176///      ...props
177///  });
178/// ```
179pub(super) fn object_define_property(
180    target: ExprOrSpread,
181    prop_name: ExprOrSpread,
182    descriptor: ExprOrSpread,
183) -> Expr {
184    member_expr!(Default::default(), DUMMY_SP, Object.defineProperty)
185        .as_call(DUMMY_SP, vec![target, prop_name, descriptor])
186}
187
188/// Creates
189///
190///```js
191/// 
192///  Object.defineProperty(exports, '__esModule', {
193///       value: true
194///  });
195/// ```
196pub(super) fn define_es_module(exports: Ident) -> Stmt {
197    object_define_property(
198        exports.as_arg(),
199        quote_str!("__esModule").as_arg(),
200        ObjectLit {
201            span: DUMMY_SP,
202            props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
203                key: PropName::Ident(quote_ident!("value")),
204                value: true.into(),
205            })))],
206        }
207        .as_arg(),
208    )
209    .into_stmt()
210}
211
212pub(super) trait VecStmtLike {
213    type StmtLike: IsDirective;
214
215    fn as_ref(&self) -> &[Self::StmtLike];
216
217    fn has_use_strict(&self) -> bool {
218        self.as_ref()
219            .iter()
220            .take_while(|s| s.directive_continue())
221            .any(IsDirective::is_use_strict)
222    }
223}
224
225impl VecStmtLike for [ModuleItem] {
226    type StmtLike = ModuleItem;
227
228    fn as_ref(&self) -> &[Self::StmtLike] {
229        self
230    }
231}
232
233impl VecStmtLike for [Stmt] {
234    type StmtLike = Stmt;
235
236    fn as_ref(&self) -> &[Self::StmtLike] {
237        self
238    }
239}
240
241pub(super) fn use_strict() -> Stmt {
242    Lit::Str(quote_str!("use strict")).into_stmt()
243}
244
245pub(crate) fn object_define_enumerable(
246    target: ExprOrSpread,
247    prop_name: ExprOrSpread,
248    prop: PropOrSpread,
249) -> Expr {
250    object_define_property(
251        target,
252        prop_name,
253        ObjectLit {
254            span: DUMMY_SP,
255            props: vec![
256                PropOrSpread::Prop(Box::new(
257                    KeyValueProp {
258                        key: quote_ident!("enumerable").into(),
259                        value: Box::new(true.into()),
260                    }
261                    .into(),
262                )),
263                prop,
264            ],
265        }
266        .as_arg(),
267    )
268}
269
270/// ```javascript
271/// function _export(target, all) {
272///     for (var name in all)
273///         Object.defineProperty(target, name, {
274///             enumerable: true,
275///             get: Object.getOwnPropertyDescriptor(all, name).get,
276///         });
277/// }
278/// ```
279/// Compatibility:
280/// - `Object.defineProperty` is supported in Node.js v0.10+
281/// - `Object.getOwnPropertyDescriptor` is supported in Node.js v0.10+
282pub(crate) fn esm_export() -> Function {
283    let target = private_ident!("target");
284    let all = private_ident!("all");
285    let name = private_ident!("name");
286
287    let getter = member_expr!(
288        Default::default(),
289        DUMMY_SP,
290        Object.getOwnPropertyDescriptor
291    )
292    .as_call(DUMMY_SP, vec![all.clone().as_arg(), name.clone().as_arg()])
293    .make_member("get".into());
294
295    // Create property descriptor with enumerable: true and get: desc.get
296    let getter = KeyValueProp {
297        key: quote_ident!("get").into(),
298        value: getter.into(),
299    };
300
301    let body = object_define_enumerable(
302        target.clone().as_arg(),
303        name.clone().as_arg(),
304        PropOrSpread::Prop(Box::new(Prop::KeyValue(getter))),
305    )
306    .into_stmt();
307
308    let for_in_stmt: Stmt = ForInStmt {
309        span: DUMMY_SP,
310        left: VarDecl {
311            decls: vec![VarDeclarator {
312                span: DUMMY_SP,
313                name: name.into(),
314                init: None,
315                definite: false,
316            }],
317            ..Default::default()
318        }
319        .into(),
320        right: Box::new(all.clone().into()),
321        body: Box::new(body),
322    }
323    .into();
324
325    Function {
326        params: vec![target.into(), all.into()],
327        body: Some(BlockStmt {
328            stmts: vec![for_in_stmt],
329            ..Default::default()
330        }),
331        ..Default::default()
332    }
333}
334
335pub(crate) fn emit_export_stmts(exports: Ident, mut prop_list: Vec<ExportKV>) -> Vec<Stmt> {
336    match prop_list.len() {
337        0 | 1 => prop_list
338            .pop()
339            .map(|(export_name, export_item)| {
340                object_define_enumerable(
341                    exports.as_arg(),
342                    quote_str!(export_item.export_name_span().0, export_name).as_arg(),
343                    prop_function((
344                        "get".into(),
345                        ExportItem::new(Default::default(), export_item.into_local_ident()),
346                    ))
347                    .into(),
348                )
349                .into_stmt()
350            })
351            .into_iter()
352            .collect(),
353        _ => {
354            let props = prop_list
355                .into_iter()
356                .map(getter_function)
357                .map(From::from)
358                .collect();
359            let obj_lit = ObjectLit {
360                span: DUMMY_SP,
361                props,
362            };
363
364            let esm_export_ident = private_ident!("_export");
365
366            vec![
367                Stmt::Decl(Decl::Fn(
368                    esm_export().into_fn_decl(esm_export_ident.clone()),
369                )),
370                esm_export_ident
371                    .as_call(DUMMY_SP, vec![exports.as_arg(), obj_lit.as_arg()])
372                    .into_stmt(),
373            ]
374        }
375    }
376}
377
378pub(crate) fn prop_name(key: &str, (span, _): SpanCtx) -> IdentOrStr {
379    if is_valid_prop_ident(key) {
380        IdentOrStr::Ident(IdentName::new(key.into(), span))
381    } else {
382        IdentOrStr::Str(quote_str!(span, key))
383    }
384}
385
386pub(crate) enum IdentOrStr {
387    Ident(IdentName),
388    Str(Str),
389}
390
391impl From<IdentOrStr> for PropName {
392    fn from(val: IdentOrStr) -> Self {
393        match val {
394            IdentOrStr::Ident(i) => Self::Ident(i),
395            IdentOrStr::Str(s) => Self::Str(s),
396        }
397    }
398}
399
400impl From<IdentOrStr> for MemberProp {
401    fn from(val: IdentOrStr) -> Self {
402        match val {
403            IdentOrStr::Ident(i) => Self::Ident(i),
404            IdentOrStr::Str(s) => Self::Computed(ComputedPropName {
405                span: DUMMY_SP,
406                expr: s.into(),
407            }),
408        }
409    }
410}
411
412/// ```javascript
413/// {
414///     key: function() {
415///         return expr;
416///     },
417/// }
418/// ```
419pub(crate) fn prop_function((key, export_item): ExportKV) -> Prop {
420    let key = prop_name(&key, export_item.export_name_span()).into();
421
422    KeyValueProp {
423        key,
424        value: Box::new(
425            export_item
426                .into_local_ident()
427                .into_lazy_fn(Default::default())
428                .into_fn_expr(None)
429                .into(),
430        ),
431    }
432    .into()
433}
434
435/// ```javascript
436/// {
437///     get key() {
438///         return expr;
439///     },
440/// }
441/// ```
442/// Compatibility: getter is supported in Node.js v0.10+
443fn getter_function((key, export_item): ExportKV) -> Prop {
444    let key = prop_name(&key, export_item.export_name_span()).into();
445
446    GetterProp {
447        key,
448        body: Some(BlockStmt {
449            stmts: vec![export_item.into_local_ident().into_return_stmt().into()],
450            ..Default::default()
451        }),
452        ..Default::default()
453    }
454    .into()
455}