swc_ecma_transforms_module/
util.rs

1use is_macro::Is;
2use serde::{Deserialize, Serialize};
3use swc_atoms::Atom;
4use swc_cached::regex::CachedRegex;
5use swc_common::DUMMY_SP;
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<CachedRegex>,
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('/').last().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#[macro_export]
271macro_rules! caniuse {
272    ($feature_set:ident . $feature:ident) => {
273        $feature_set.intersects(swc_ecma_transforms_base::feature::FeatureFlag::$feature)
274    };
275}
276
277/// ```javascript
278/// function _esmExport(target, all) {
279///    for (var name in all)Object.defineProperty(target, name, { get: all[name], enumerable: true });
280/// }
281/// ```
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 = KeyValueProp {
288        key: quote_ident!("get").into(),
289        value: all.clone().computed_member(name.clone()).into(),
290    };
291
292    let body = object_define_enumerable(
293        target.clone().as_arg(),
294        name.clone().as_arg(),
295        PropOrSpread::Prop(Box::new(Prop::KeyValue(getter))),
296    )
297    .into_stmt();
298
299    let for_in_stmt: Stmt = ForInStmt {
300        span: DUMMY_SP,
301        left: VarDecl {
302            span: DUMMY_SP,
303            kind: VarDeclKind::Var,
304            declare: false,
305            decls: vec![VarDeclarator {
306                span: DUMMY_SP,
307                name: name.into(),
308                init: None,
309                definite: false,
310            }],
311            ..Default::default()
312        }
313        .into(),
314        right: Box::new(all.clone().into()),
315        body: Box::new(body),
316    }
317    .into();
318
319    Function {
320        params: vec![target.into(), all.into()],
321        decorators: Default::default(),
322        span: DUMMY_SP,
323        body: Some(BlockStmt {
324            stmts: vec![for_in_stmt],
325            ..Default::default()
326        }),
327        is_generator: false,
328        is_async: false,
329        ..Default::default()
330    }
331}
332
333pub(crate) fn emit_export_stmts(exports: Ident, mut prop_list: Vec<ExportKV>) -> Vec<Stmt> {
334    match prop_list.len() {
335        0 | 1 => prop_list
336            .pop()
337            .map(|(export_name, export_item)| {
338                object_define_enumerable(
339                    exports.as_arg(),
340                    quote_str!(export_item.export_name_span().0, export_name).as_arg(),
341                    prop_function((
342                        "get".into(),
343                        ExportItem::new(Default::default(), export_item.into_local_ident()),
344                    ))
345                    .into(),
346                )
347                .into_stmt()
348            })
349            .into_iter()
350            .collect(),
351        _ => {
352            let props = prop_list
353                .into_iter()
354                .map(prop_function)
355                .map(From::from)
356                .collect();
357            let obj_lit = ObjectLit {
358                span: DUMMY_SP,
359                props,
360            };
361
362            let esm_export_ident = private_ident!("_export");
363
364            vec![
365                Stmt::Decl(Decl::Fn(
366                    esm_export().into_fn_decl(esm_export_ident.clone()),
367                )),
368                esm_export_ident
369                    .as_call(DUMMY_SP, vec![exports.as_arg(), obj_lit.as_arg()])
370                    .into_stmt(),
371            ]
372        }
373    }
374}
375
376pub(crate) fn prop_name(key: &str, (span, _): SpanCtx) -> IdentOrStr {
377    if is_valid_prop_ident(key) {
378        IdentOrStr::Ident(IdentName::new(key.into(), span))
379    } else {
380        IdentOrStr::Str(quote_str!(span, key))
381    }
382}
383
384pub(crate) enum IdentOrStr {
385    Ident(IdentName),
386    Str(Str),
387}
388
389impl From<IdentOrStr> for PropName {
390    fn from(val: IdentOrStr) -> Self {
391        match val {
392            IdentOrStr::Ident(i) => Self::Ident(i),
393            IdentOrStr::Str(s) => Self::Str(s),
394        }
395    }
396}
397
398impl From<IdentOrStr> for MemberProp {
399    fn from(val: IdentOrStr) -> Self {
400        match val {
401            IdentOrStr::Ident(i) => Self::Ident(i),
402            IdentOrStr::Str(s) => Self::Computed(ComputedPropName {
403                span: DUMMY_SP,
404                expr: s.into(),
405            }),
406        }
407    }
408}
409
410/// ```javascript
411/// {
412///     key: function() {
413///         return expr;
414///     },
415/// }
416/// ```
417pub(crate) fn prop_function((key, export_item): ExportKV) -> Prop {
418    let key = prop_name(&key, export_item.export_name_span()).into();
419
420    KeyValueProp {
421        key,
422        value: Box::new(
423            export_item
424                .into_local_ident()
425                .into_lazy_fn(Default::default())
426                .into_fn_expr(None)
427                .into(),
428        ),
429    }
430    .into()
431}