swc_ecma_transforms_module/
util.rs1use 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 #[serde(default)]
38 pub export_interop_annotation: Option<bool>,
39 #[serde(default)]
40 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
171pub(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
188pub(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
270pub(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 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
412pub(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
435fn 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}