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}