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}