swc_ecma_compat_es2015/
template_literal.rs

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