swc_ecma_compat_es2015/
template_literal.rs

1use std::{iter, mem};
2
3use serde_derive::Deserialize;
4use swc_atoms::{atom, Atom};
5use swc_common::{util::take::Take, BytePos, Spanned, DUMMY_SP};
6use swc_ecma_ast::*;
7use swc_ecma_transforms_base::{helper, perf::Parallel};
8use swc_ecma_utils::{is_literal, prepend_stmts, private_ident, quote_ident, ExprFactory};
9use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
10use swc_trace_macro::swc_trace;
11
12pub fn template_literal(c: Config) -> impl Pass {
13    visit_mut_pass(TemplateLiteral {
14        c,
15        ..Default::default()
16    })
17}
18
19#[derive(Default)]
20struct TemplateLiteral {
21    added: Vec<Stmt>,
22    c: Config,
23}
24
25#[derive(Debug, Clone, Copy, Default, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct Config {
28    #[serde(default)]
29    pub ignore_to_primitive: bool,
30    #[serde(default)]
31    pub mutable_template: bool,
32}
33
34impl Parallel for TemplateLiteral {
35    fn create(&self) -> Self {
36        Self::default()
37    }
38
39    fn merge(&mut self, other: Self) {
40        self.added.extend(other.added);
41    }
42}
43
44#[swc_trace]
45impl VisitMut for TemplateLiteral {
46    noop_visit_mut_type!(fail);
47
48    fn visit_mut_expr(&mut self, e: &mut Expr) {
49        e.visit_mut_children_with(self);
50
51        match e {
52            Expr::Tpl(Tpl {
53                span,
54                exprs,
55                quasis,
56                ..
57            }) => {
58                assert_eq!(quasis.len(), exprs.len() + 1);
59
60                // This makes result of addition string
61                let mut obj: Box<Expr> = Box::new(
62                    Lit::Str({
63                        let s = quasis[0]
64                            .cooked
65                            .clone()
66                            .unwrap_or_else(|| quasis[0].raw.clone());
67
68                        Str {
69                            span: quasis[0].span,
70                            value: Atom::from(&*s),
71                            raw: None,
72                        }
73                    })
74                    .into(),
75                );
76
77                let len = quasis.len() + exprs.len();
78
79                let mut args = Vec::new();
80                let mut quasis = quasis.iter_mut();
81                let mut exprs = exprs.take().into_iter();
82
83                for i in 0..len {
84                    if i == 0 {
85                        quasis.next();
86
87                        if len == 1 {
88                            obj.set_span(*span);
89                        }
90                        continue;
91                    }
92                    let last = i == len - 1;
93
94                    let expr = if i % 2 == 0 {
95                        // Quasis
96                        match quasis.next() {
97                            Some(TplElement {
98                                span, cooked, raw, ..
99                            }) => {
100                                let s = cooked.clone().unwrap_or_else(|| raw.clone());
101
102                                Box::new(
103                                    Lit::Str(Str {
104                                        span: *span,
105                                        value: (&*s).into(),
106                                        raw: None,
107                                    })
108                                    .into(),
109                                )
110                            }
111                            _ => unreachable!(),
112                        }
113                    } else {
114                        // Expression
115                        exprs.next().unwrap()
116                    };
117
118                    let expr_span = expr.span();
119
120                    // We can optimize if expression is a literal or ident
121                    let is_lit = is_literal(&expr);
122
123                    if is_lit {
124                        let is_empty = match &*expr {
125                            Expr::Lit(Lit::Str(Str { value, .. })) => value.is_empty(),
126                            _ => false,
127                        };
128
129                        if !is_empty && args.is_empty() {
130                            if let Expr::Lit(Lit::Str(Str { span, value, raw })) = *obj {
131                                match *expr {
132                                    Expr::Lit(Lit::Str(Str {
133                                        span: r_span,
134                                        value: r_value,
135                                        ..
136                                    })) => {
137                                        obj = Lit::Str(Str {
138                                            span: span.with_hi(r_span.hi()),
139                                            raw: None,
140                                            value: format!("{value}{r_value}").into(),
141                                        })
142                                        .into();
143                                        continue;
144                                    }
145                                    _ => {
146                                        obj = Lit::Str(Str { span, raw, value }).into();
147                                    }
148                                }
149                            }
150                        }
151
152                        if !is_empty {
153                            args.push(expr);
154                        }
155
156                        if last && !args.is_empty() {
157                            obj = if self.c.ignore_to_primitive {
158                                let args = mem::take(&mut args);
159                                for arg in args {
160                                    obj = BinExpr {
161                                        span: span.with_hi(expr_span.hi() + BytePos(1)),
162                                        op: op!(bin, "+"),
163                                        left: obj,
164                                        right: arg,
165                                    }
166                                    .into()
167                                }
168                                obj
169                            } else {
170                                CallExpr {
171                                    span: span.with_hi(expr_span.hi() + BytePos(1)),
172                                    callee: MemberExpr {
173                                        span: DUMMY_SP,
174                                        obj,
175                                        prop: MemberProp::Ident(IdentName::new(
176                                            atom!("concat"),
177                                            expr_span,
178                                        )),
179                                    }
180                                    .as_callee(),
181                                    args: mem::take(&mut args)
182                                        .into_iter()
183                                        .map(|expr| expr.as_arg())
184                                        .collect(),
185                                    ..Default::default()
186                                }
187                                .into()
188                            }
189                        }
190                    } else {
191                        if !args.is_empty() {
192                            obj = if self.c.ignore_to_primitive {
193                                let args = mem::take(&mut args);
194                                let len = args.len();
195                                for arg in args {
196                                    // for `${asd}a`
197                                    if let Expr::Lit(Lit::Str(s)) = obj.as_ref() {
198                                        if s.value.is_empty() && len == 2 {
199                                            obj = arg;
200                                            continue;
201                                        }
202                                    }
203                                    obj = BinExpr {
204                                        span: span.with_hi(expr_span.hi() + BytePos(1)),
205                                        op: op!(bin, "+"),
206                                        left: obj,
207                                        right: arg,
208                                    }
209                                    .into()
210                                }
211                                obj
212                            } else {
213                                CallExpr {
214                                    span: span.with_hi(expr_span.hi() + BytePos(1)),
215                                    callee: MemberExpr {
216                                        span: DUMMY_SP,
217                                        obj,
218                                        prop: MemberProp::Ident(IdentName::new(
219                                            atom!("concat"),
220                                            expr_span,
221                                        )),
222                                    }
223                                    .as_callee(),
224                                    args: mem::take(&mut args)
225                                        .into_iter()
226                                        .map(|expr| expr.as_arg())
227                                        .collect(),
228                                    ..Default::default()
229                                }
230                                .into()
231                            };
232                        }
233                        debug_assert!(args.is_empty());
234
235                        args.push(expr);
236                    }
237                }
238
239                *e = *obj
240            }
241
242            Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => {
243                assert_eq!(tpl.quasis.len(), tpl.exprs.len() + 1);
244
245                let fn_ident = private_ident!("_templateObject");
246
247                let f = Function {
248                    span: DUMMY_SP,
249                    is_async: false,
250                    is_generator: false,
251                    params: Vec::new(),
252                    body: {
253                        // const data = _tagged_template_literal(["first", "second"]);
254                        let data_decl = VarDecl {
255                            span: DUMMY_SP,
256                            kind: VarDeclKind::Const,
257                            declare: false,
258                            decls: vec![VarDeclarator {
259                                span: DUMMY_SP,
260                                name: quote_ident!("data").into(),
261                                definite: false,
262                                init: Some(Box::new(Expr::Call(CallExpr {
263                                    span: DUMMY_SP,
264                                    callee: if self.c.mutable_template {
265                                        helper!(tagged_template_literal_loose)
266                                    } else {
267                                        helper!(tagged_template_literal)
268                                    },
269                                    args: {
270                                        let has_escape =
271                                            tpl.quasis.iter().any(|s| s.raw.contains('\\'));
272
273                                        let raw = if has_escape {
274                                            Some(
275                                                ArrayLit {
276                                                    span: DUMMY_SP,
277                                                    elems: tpl
278                                                        .quasis
279                                                        .iter()
280                                                        .cloned()
281                                                        .map(|elem| elem.raw.as_arg())
282                                                        .map(Some)
283                                                        .collect(),
284                                                }
285                                                .as_arg(),
286                                            )
287                                        } else {
288                                            None
289                                        };
290
291                                        iter::once(
292                                            ArrayLit {
293                                                span: DUMMY_SP,
294                                                elems: tpl
295                                                    .quasis
296                                                    .take()
297                                                    .into_iter()
298                                                    .map(|elem| match elem.cooked {
299                                                        Some(cooked) => cooked.as_arg(),
300                                                        None => Expr::undefined(DUMMY_SP).as_arg(),
301                                                    })
302                                                    .map(Some)
303                                                    .collect(),
304                                            }
305                                            .as_arg(),
306                                        )
307                                        .chain(raw)
308                                        .collect()
309                                    },
310                                    ..Default::default()
311                                }))),
312                            }],
313                            ..Default::default()
314                        };
315
316                        // _templateObject2 = function () {
317                        //     return data;
318                        // };
319                        let assign_expr: Expr = {
320                            AssignExpr {
321                                span: DUMMY_SP,
322                                left: fn_ident.clone().into(),
323                                op: op!("="),
324                                right: Function {
325                                    span: DUMMY_SP,
326                                    is_async: false,
327                                    is_generator: false,
328                                    params: Vec::new(),
329                                    body: Some(BlockStmt {
330                                        span: DUMMY_SP,
331                                        stmts: vec![Stmt::Return(ReturnStmt {
332                                            span: DUMMY_SP,
333                                            arg: Some(Box::new(quote_ident!("data").into())),
334                                        })],
335                                        ..Default::default()
336                                    }),
337                                    ..Default::default()
338                                }
339                                .into(),
340                            }
341                            .into()
342                        };
343
344                        Some(BlockStmt {
345                            span: DUMMY_SP,
346
347                            stmts: vec![
348                                data_decl.into(),
349                                assign_expr.into_stmt(),
350                                Stmt::Return(ReturnStmt {
351                                    span: DUMMY_SP,
352                                    arg: Some(Box::new(quote_ident!("data").into())),
353                                }),
354                            ],
355                            ..Default::default()
356                        })
357                    },
358
359                    ..Default::default()
360                };
361                self.added.push(
362                    FnDecl {
363                        declare: false,
364                        ident: fn_ident.clone(),
365                        function: f.into(),
366                    }
367                    .into(),
368                );
369
370                *e = CallExpr {
371                    span: DUMMY_SP,
372                    callee: tag.take().as_callee(),
373                    args: iter::once(
374                        CallExpr {
375                            span: DUMMY_SP,
376                            callee: fn_ident.as_callee(),
377                            args: Vec::new(),
378                            ..Default::default()
379                        }
380                        .as_arg(),
381                    )
382                    .chain(tpl.exprs.take().into_iter().map(|e| e.as_arg()))
383                    .collect(),
384                    ..Default::default()
385                }
386                .into()
387            }
388
389            _ => {}
390        }
391    }
392
393    fn visit_mut_module(&mut self, m: &mut Module) {
394        m.visit_mut_children_with(self);
395
396        prepend_stmts(&mut m.body, self.added.drain(..).map(ModuleItem::from));
397    }
398
399    fn visit_mut_script(&mut self, m: &mut Script) {
400        m.visit_mut_children_with(self);
401
402        prepend_stmts(&mut m.body, self.added.drain(..));
403    }
404}