swc_ecma_transforms_module/
util.rs1use 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 #[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<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
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
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
277pub(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
410pub(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}