swc_ecma_compat_bugfixes/
template_literal_caching.rs1use swc_common::DUMMY_SP;
2use swc_ecma_ast::*;
3use swc_ecma_utils::{prepend_stmt, private_ident, ExprFactory};
4use swc_ecma_visit::{fold_pass, standard_only_fold, Fold, FoldWith};
5use swc_trace_macro::swc_trace;
6
7pub fn template_literal_caching() -> impl Pass {
23 fold_pass(TemplateLiteralCaching::default())
24}
25#[derive(Default, Clone)]
26struct TemplateLiteralCaching {
27 decls: Vec<VarDeclarator>,
28 helper_ident: Option<Ident>,
29}
30
31impl TemplateLiteralCaching {
32 fn create_binding(&mut self, name: Ident, init: Option<Expr>) {
33 let init = init.map(Box::new);
34 self.decls.push(VarDeclarator {
35 span: DUMMY_SP,
36 name: name.into(),
37 init,
38 definite: false,
39 })
40 }
41
42 fn create_var_decl(&mut self) -> Option<Stmt> {
43 if !self.decls.is_empty() {
44 return Some(
45 VarDecl {
46 span: DUMMY_SP,
47 kind: VarDeclKind::Let,
48 declare: false,
49 decls: self.decls.clone(),
50 ..Default::default()
51 }
52 .into(),
53 );
54 }
55 None
56 }
57}
58
59#[swc_trace]
61impl Fold for TemplateLiteralCaching {
62 standard_only_fold!();
63
64 fn fold_expr(&mut self, n: Expr) -> Expr {
65 let n = n.fold_children_with(self);
66 match n {
67 Expr::TaggedTpl(n) => {
68 if self.helper_ident.is_none() {
69 let helper_ident = private_ident!("_");
72 let t = private_ident!("t");
73 self.helper_ident = Some(helper_ident.clone());
74 self.create_binding(
75 helper_ident,
76 Some(
77 ArrowExpr {
78 span: DUMMY_SP,
79 params: vec![t.clone().into()],
80 body: Box::new(BlockStmtOrExpr::Expr(t.into())),
81 is_async: false,
82 is_generator: false,
83 ..Default::default()
84 }
85 .into(),
86 ),
87 )
88 }
89
90 let helper_ident = self.helper_ident.as_ref().unwrap();
91
92 let template = TaggedTpl {
96 span: DUMMY_SP,
97 tag: helper_ident.clone().into(),
98 tpl: Box::new(Tpl {
99 span: DUMMY_SP,
100 quasis: n.tpl.quasis,
101 exprs: n.tpl.exprs.iter().map(|_| 0.0.into()).collect(),
102 }),
103 ..Default::default()
104 };
105
106 let t = private_ident!("t");
109 self.create_binding(t.clone(), None);
110 let inline_cache: Expr = BinExpr {
111 span: DUMMY_SP,
112 op: op!("||"),
113 left: t.clone().into(),
114 right: AssignExpr {
115 span: DUMMY_SP,
116 op: op!("="),
117 left: t.into(),
118 right: Box::new(Expr::TaggedTpl(template)),
119 }
120 .into(),
121 }
122 .into();
123
124 CallExpr {
129 span: DUMMY_SP,
130 callee: n.tag.as_callee(),
131 args: vec![inline_cache.as_arg()]
132 .into_iter()
133 .chain(n.tpl.exprs.into_iter().map(|expr| expr.as_arg()))
134 .collect(),
135 ..Default::default()
136 }
137 .into()
138 }
139 _ => n,
140 }
141 }
142
143 fn fold_module(&mut self, n: Module) -> Module {
144 let mut body = n.body.fold_children_with(self);
145 if let Some(var) = self.create_var_decl() {
146 prepend_stmt(&mut body, var.into())
147 }
148
149 Module { body, ..n }
150 }
151
152 fn fold_script(&mut self, n: Script) -> Script {
153 let mut body = n.body.fold_children_with(self);
154 if let Some(var) = self.create_var_decl() {
155 prepend_stmt(&mut body, var)
156 }
157
158 Script { body, ..n }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use swc_common::Mark;
165 use swc_ecma_transforms_base::resolver;
166 use swc_ecma_transforms_testing::test;
167
168 use super::*;
169
170 fn tr() -> impl Pass {
171 (
172 resolver(Mark::new(), Mark::new(), false),
173 template_literal_caching(),
174 )
175 }
176
177 test!(
178 ::swc_ecma_parser::Syntax::default(),
179 |_| tr(),
180 single_tag,
181 "t`a`;"
182 );
183
184 test!(
185 ::swc_ecma_parser::Syntax::default(),
186 |_| tr(),
187 single_tag_empty,
188 "x``;"
189 );
190
191 test!(
192 ::swc_ecma_parser::Syntax::default(),
193 |_| tr(),
194 multiple_tags,
195 r#"
196 t`a`;
197 x``;
198 "#
199 );
200
201 test!(
202 ::swc_ecma_parser::Syntax::default(),
203 |_| tr(),
204 function_scoped_tag,
205 "const f = t => t`a`;"
206 );
207
208 test!(
209 ::swc_ecma_parser::Syntax::default(),
210 |_| tr(),
211 dynamic_tag,
212 "fn()``;"
213 );
214
215 test!(
216 ::swc_ecma_parser::Syntax::default(),
217 |_| tr(),
218 dynamic_expressions,
219 "const f = t => t`a${1}b${t}${[\"hello\"]}`;"
220 );
221
222 test!(
223 ::swc_ecma_parser::Syntax::default(),
224 |_| tr(),
225 same_tag_safari_11,
226 "x`a` === x`a`;"
227 );
228
229 test!(
230 ::swc_ecma_parser::Syntax::default(),
231 |_| tr(),
232 shared_strings_safari_11,
233 "x`a` === y`a`;"
234 );
235
236 test!(
237 ::swc_ecma_parser::Syntax::default(),
238 |_| tr(),
239 template_literals,
240 r#"
241 `a`;
242 t(`a`);
243 t;
244 `a`;
245 "#
246 );
247
248 test!(
249 ::swc_ecma_parser::Syntax::default(),
250 |_| tr(),
251 prevent_tag_collision,
252 r#"
253 const _ = 1;
254 t``;
255 "#
256 );
257
258 test!(
259 ::swc_ecma_parser::Syntax::default(),
260 |_| tr(),
261 block_scoped_tag,
262 "for (let t of []) t`a`;"
263 );
264}