swc_ecma_compat_es2015/
template_literal.rs

1use std::{iter, mem};
2
3use serde_derive::Deserialize;
4use swc_atoms::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                        continue;
87                    }
88                    let last = i == len - 1;
89
90                    let expr = if i % 2 == 0 {
91                        // Quasis
92                        match quasis.next() {
93                            Some(TplElement {
94                                span, cooked, raw, ..
95                            }) => {
96                                let s = cooked.clone().unwrap_or_else(|| raw.clone());
97
98                                Box::new(
99                                    Lit::Str(Str {
100                                        span: *span,
101                                        value: (&*s).into(),
102                                        raw: None,
103                                    })
104                                    .into(),
105                                )
106                            }
107                            _ => unreachable!(),
108                        }
109                    } else {
110                        // Expression
111                        exprs.next().unwrap()
112                    };
113
114                    let expr_span = expr.span();
115
116                    // We can optimize if expression is a literal or ident
117                    let is_lit = is_literal(&expr);
118
119                    if is_lit {
120                        let is_empty = match &*expr {
121                            Expr::Lit(Lit::Str(Str { value, .. })) => value.is_empty(),
122                            _ => false,
123                        };
124
125                        if !is_empty && args.is_empty() {
126                            if let Expr::Lit(Lit::Str(Str { span, value, raw })) = *obj {
127                                match *expr {
128                                    Expr::Lit(Lit::Str(Str {
129                                        span: r_span,
130                                        value: r_value,
131                                        ..
132                                    })) => {
133                                        obj = Lit::Str(Str {
134                                            span: span.with_hi(r_span.hi()),
135                                            raw: None,
136                                            value: format!("{}{}", value, r_value).into(),
137                                        })
138                                        .into();
139                                        continue;
140                                    }
141                                    _ => {
142                                        obj = Lit::Str(Str { span, raw, value }).into();
143                                    }
144                                }
145                            }
146                        }
147
148                        if !is_empty {
149                            args.push(expr);
150                        }
151
152                        if last && !args.is_empty() {
153                            obj = if self.c.ignore_to_primitive {
154                                let args = mem::take(&mut args);
155                                for arg in args {
156                                    obj = BinExpr {
157                                        span: span.with_hi(expr_span.hi() + BytePos(1)),
158                                        op: op!(bin, "+"),
159                                        left: obj,
160                                        right: arg,
161                                    }
162                                    .into()
163                                }
164                                obj
165                            } else {
166                                CallExpr {
167                                    span: span.with_hi(expr_span.hi() + BytePos(1)),
168                                    callee: MemberExpr {
169                                        span: DUMMY_SP,
170                                        obj,
171                                        prop: MemberProp::Ident(IdentName::new(
172                                            "concat".into(),
173                                            expr_span,
174                                        )),
175                                    }
176                                    .as_callee(),
177                                    args: mem::take(&mut args)
178                                        .into_iter()
179                                        .map(|expr| expr.as_arg())
180                                        .collect(),
181                                    ..Default::default()
182                                }
183                                .into()
184                            }
185                        }
186                    } else {
187                        if !args.is_empty() {
188                            obj = if self.c.ignore_to_primitive {
189                                let args = mem::take(&mut args);
190                                let len = args.len();
191                                for arg in args {
192                                    // for `${asd}a`
193                                    if let Expr::Lit(Lit::Str(s)) = obj.as_ref() {
194                                        if s.value.len() == 0 && len == 2 {
195                                            obj = arg;
196                                            continue;
197                                        }
198                                    }
199                                    obj = BinExpr {
200                                        span: span.with_hi(expr_span.hi() + BytePos(1)),
201                                        op: op!(bin, "+"),
202                                        left: obj,
203                                        right: arg,
204                                    }
205                                    .into()
206                                }
207                                obj
208                            } else {
209                                CallExpr {
210                                    span: span.with_hi(expr_span.hi() + BytePos(1)),
211                                    callee: MemberExpr {
212                                        span: DUMMY_SP,
213                                        obj,
214                                        prop: MemberProp::Ident(IdentName::new(
215                                            "concat".into(),
216                                            expr_span,
217                                        )),
218                                    }
219                                    .as_callee(),
220                                    args: mem::take(&mut args)
221                                        .into_iter()
222                                        .map(|expr| expr.as_arg())
223                                        .collect(),
224                                    ..Default::default()
225                                }
226                                .into()
227                            };
228                        }
229                        debug_assert!(args.is_empty());
230
231                        args.push(expr);
232                    }
233                }
234
235                *e = *obj
236            }
237
238            Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => {
239                assert_eq!(tpl.quasis.len(), tpl.exprs.len() + 1);
240
241                let fn_ident = private_ident!("_templateObject");
242
243                let f = Function {
244                    span: DUMMY_SP,
245                    is_async: false,
246                    is_generator: false,
247                    params: Vec::new(),
248                    body: {
249                        // const data = _tagged_template_literal(["first", "second"]);
250                        let data_decl = VarDecl {
251                            span: DUMMY_SP,
252                            kind: VarDeclKind::Const,
253                            declare: false,
254                            decls: vec![VarDeclarator {
255                                span: DUMMY_SP,
256                                name: quote_ident!("data").into(),
257                                definite: false,
258                                init: Some(Box::new(Expr::Call(CallExpr {
259                                    span: DUMMY_SP,
260                                    callee: if self.c.mutable_template {
261                                        helper!(tagged_template_literal_loose)
262                                    } else {
263                                        helper!(tagged_template_literal)
264                                    },
265                                    args: {
266                                        let has_escape =
267                                            tpl.quasis.iter().any(|s| s.raw.contains('\\'));
268
269                                        let raw = if has_escape {
270                                            Some(
271                                                ArrayLit {
272                                                    span: DUMMY_SP,
273                                                    elems: tpl
274                                                        .quasis
275                                                        .iter()
276                                                        .cloned()
277                                                        .map(|elem| elem.raw.as_arg())
278                                                        .map(Some)
279                                                        .collect(),
280                                                }
281                                                .as_arg(),
282                                            )
283                                        } else {
284                                            None
285                                        };
286
287                                        iter::once(
288                                            ArrayLit {
289                                                span: DUMMY_SP,
290                                                elems: tpl
291                                                    .quasis
292                                                    .take()
293                                                    .into_iter()
294                                                    .map(|elem| match elem.cooked {
295                                                        Some(cooked) => cooked.as_arg(),
296                                                        None => Expr::undefined(DUMMY_SP).as_arg(),
297                                                    })
298                                                    .map(Some)
299                                                    .collect(),
300                                            }
301                                            .as_arg(),
302                                        )
303                                        .chain(raw)
304                                        .collect()
305                                    },
306                                    ..Default::default()
307                                }))),
308                            }],
309                            ..Default::default()
310                        };
311
312                        // _templateObject2 = function () {
313                        //     return data;
314                        // };
315                        let assign_expr: Expr = {
316                            AssignExpr {
317                                span: DUMMY_SP,
318                                left: fn_ident.clone().into(),
319                                op: op!("="),
320                                right: Function {
321                                    span: DUMMY_SP,
322                                    is_async: false,
323                                    is_generator: false,
324                                    params: Vec::new(),
325                                    body: Some(BlockStmt {
326                                        span: DUMMY_SP,
327                                        stmts: vec![Stmt::Return(ReturnStmt {
328                                            span: DUMMY_SP,
329                                            arg: Some(Box::new(quote_ident!("data").into())),
330                                        })],
331                                        ..Default::default()
332                                    }),
333                                    ..Default::default()
334                                }
335                                .into(),
336                            }
337                            .into()
338                        };
339
340                        Some(BlockStmt {
341                            span: DUMMY_SP,
342
343                            stmts: vec![
344                                data_decl.into(),
345                                assign_expr.into_stmt(),
346                                Stmt::Return(ReturnStmt {
347                                    span: DUMMY_SP,
348                                    arg: Some(Box::new(quote_ident!("data").into())),
349                                }),
350                            ],
351                            ..Default::default()
352                        })
353                    },
354
355                    ..Default::default()
356                };
357                self.added.push(
358                    FnDecl {
359                        declare: false,
360                        ident: fn_ident.clone(),
361                        function: f.into(),
362                    }
363                    .into(),
364                );
365
366                *e = CallExpr {
367                    span: DUMMY_SP,
368                    callee: tag.take().as_callee(),
369                    args: iter::once(
370                        CallExpr {
371                            span: DUMMY_SP,
372                            callee: fn_ident.as_callee(),
373                            args: Vec::new(),
374                            ..Default::default()
375                        }
376                        .as_arg(),
377                    )
378                    .chain(tpl.exprs.take().into_iter().map(|e| e.as_arg()))
379                    .collect(),
380                    ..Default::default()
381                }
382                .into()
383            }
384
385            _ => {}
386        }
387    }
388
389    fn visit_mut_module(&mut self, m: &mut Module) {
390        m.visit_mut_children_with(self);
391
392        prepend_stmts(&mut m.body, self.added.drain(..).map(ModuleItem::from));
393    }
394
395    fn visit_mut_script(&mut self, m: &mut Script) {
396        m.visit_mut_children_with(self);
397
398        prepend_stmts(&mut m.body, self.added.drain(..));
399    }
400}