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}