1use swc_common::{util::take::Take, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_transforms_base::helper;
4use swc_ecma_utils::{
5 private_ident, quote_ident, stack_size::maybe_grow_default, ExprFactory, ModuleItemLike,
6 StmtLike,
7};
8use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
9
10pub fn explicit_resource_management() -> impl Pass {
11 visit_mut_pass(ExplicitResourceManagement::default())
12}
13
14#[derive(Default)]
15struct ExplicitResourceManagement {
16 state: Option<State>,
17
18 is_not_top_level: bool,
19}
20
21struct State {
22 env: Ident,
23 has_await: bool,
24
25 vars: Vec<(Pat, Box<Expr>)>,
26}
27
28impl Default for State {
29 fn default() -> Self {
30 Self {
31 env: private_ident!("env"),
32 has_await: false,
33 vars: Vec::new(),
34 }
35 }
36}
37
38impl ExplicitResourceManagement {
39 fn visit_mut_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
40 where
41 T: StmtLike + ModuleItemLike,
42 Vec<T>: VisitMutWith<Self>,
43 {
44 let old_state = self.state.take();
45
46 stmts.visit_mut_children_with(self);
47
48 if let Some(state) = self.state.take() {
49 self.wrap_with_try(state, stmts);
50 }
51
52 self.state = old_state;
53 }
54
55 fn wrap_with_try<T>(&mut self, state: State, stmts: &mut Vec<T>)
56 where
57 T: StmtLike + ModuleItemLike,
58 {
59 let mut new = Vec::new();
60 let mut extras = Vec::new();
61
62 let env = state.env;
63 let catch_e = private_ident!("e");
64
65 let is_async = state.has_await;
66
67 new.push(T::from(Stmt::Decl(Decl::Var(Box::new(VarDecl {
69 span: DUMMY_SP,
70 kind: VarDeclKind::Const,
71 declare: false,
72 decls: vec![VarDeclarator {
73 span: DUMMY_SP,
74 name: Pat::Ident(env.clone().into()),
75 init: Some(Box::new(Expr::Object(ObjectLit {
76 span: DUMMY_SP,
77 props: vec![
78 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
79 key: PropName::Ident(quote_ident!("stack")),
80 value: Box::new(Expr::Array(ArrayLit {
81 span: DUMMY_SP,
82 elems: vec![],
83 })),
84 }))),
85 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
86 key: PropName::Ident(quote_ident!("error")),
87 value: Expr::undefined(DUMMY_SP),
88 }))),
89 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
90 key: PropName::Ident(quote_ident!("hasError")),
91 value: Box::new(Expr::Lit(Lit::Bool(Bool {
92 span: DUMMY_SP,
93 value: false,
94 }))),
95 }))),
96 ],
97 }))),
98 definite: false,
99 }],
100 ..Default::default()
101 })))));
102
103 let mut try_block = BlockStmt {
104 stmts: vec![],
105 ..Default::default()
106 };
107
108 for (name, disposable) in state.vars {
109 let init_var_decl = Stmt::Decl(Decl::Var(Box::new(VarDecl {
110 span: DUMMY_SP,
111 kind: VarDeclKind::Const,
112 declare: false,
113 decls: vec![VarDeclarator {
114 span: DUMMY_SP,
115 name,
116 init: Some(
117 CallExpr {
118 callee: helper!(ts, ts_add_disposable_resource),
119 args: vec![
120 env.clone().as_arg(),
121 disposable.as_arg(),
122 is_async.as_arg(),
123 ],
124 ..Default::default()
125 }
126 .into(),
127 ),
128 definite: false,
129 }],
130 ..Default::default()
131 })));
132
133 try_block.stmts.push(init_var_decl);
134 }
135
136 for stmt in stmts.take() {
137 match stmt.try_into_stmt() {
138 Ok(mut stmt) => match stmt {
139 Stmt::Decl(Decl::Fn(..)) if !self.is_not_top_level => {
141 extras.push(stmt.into());
142 }
143 Stmt::Decl(Decl::Class(ClassDecl { ident, class, .. }))
144 if !self.is_not_top_level =>
145 {
146 try_block.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
148 decls: vec![VarDeclarator {
149 span: DUMMY_SP,
150 name: Pat::Ident(ident.clone().into()),
151 init: Some(
152 ClassExpr {
153 ident: Some(ident),
154 class,
155 }
156 .into(),
157 ),
158 definite: false,
159 }],
160 ..Default::default()
161 }))));
162 }
163 Stmt::Decl(Decl::Var(ref mut var)) if !self.is_not_top_level => {
165 var.kind = VarDeclKind::Var;
166 try_block.stmts.push(stmt);
167 }
168 _ => {
169 try_block.stmts.push(stmt);
170 }
171 },
172 Err(t) => match t.try_into_module_decl() {
173 Ok(ModuleDecl::ExportDecl(ExportDecl {
174 decl: Decl::Class(ClassDecl { ident, class, .. }),
175 span,
176 })) => {
177 extras.push(
179 T::try_from_module_decl(ModuleDecl::ExportNamed(NamedExport {
180 specifiers: vec![ExportNamedSpecifier {
181 span: DUMMY_SP,
182 orig: ident.clone().into(),
183 exported: None,
184 is_type_only: false,
185 }
186 .into()],
187 ..NamedExport::dummy()
188 }))
189 .unwrap(),
190 );
191
192 try_block.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
194 decls: vec![VarDeclarator {
195 span: DUMMY_SP,
196 name: Pat::Ident(ident.clone().into()),
197 init: Some(
198 ClassExpr {
199 ident: Some(ident),
200 class,
201 }
202 .into(),
203 ),
204 definite: false,
205 }],
206 span,
207 ..Default::default()
208 }))));
209 }
210 Ok(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
211 decl: DefaultDecl::Class(class_expr),
212 span,
213 ..
214 })) => {
215 let ident = class_expr
216 .ident
217 .clone()
218 .unwrap_or_else(|| private_ident!("_default"));
219
220 extras.push(
222 T::try_from_module_decl(ModuleDecl::ExportNamed(NamedExport {
223 specifiers: vec![ExportNamedSpecifier {
224 span: DUMMY_SP,
225 orig: ident.clone().into(),
226 exported: Some(quote_ident!("default").into()),
227 is_type_only: false,
228 }
229 .into()],
230 ..NamedExport::dummy()
231 }))
232 .unwrap(),
233 );
234
235 try_block.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
238 decls: vec![VarDeclarator {
239 span: DUMMY_SP,
240 name: Pat::Ident(ident.into()),
241 init: Some(class_expr.into()),
242 definite: false,
243 }],
244 span,
245 ..Default::default()
246 }))));
247 }
248 Ok(module_decl) => {
249 extras.push(T::try_from_module_decl(module_decl).unwrap());
250 }
251 Err(t) => extras.push(t),
252 },
253 }
254 }
255
256 let catch_clause = CatchClause {
257 span: DUMMY_SP,
258 param: Some(Pat::Ident(catch_e.clone().into())),
259 body: BlockStmt {
260 span: DUMMY_SP,
261 stmts: vec![
262 AssignExpr {
264 span: DUMMY_SP,
265 left: MemberExpr {
266 span: DUMMY_SP,
267 obj: Box::new(env.clone().into()),
268 prop: MemberProp::Ident(quote_ident!("error")),
269 }
270 .into(),
271 op: op!("="),
272 right: Box::new(catch_e.clone().into()),
273 }
274 .into_stmt(),
275 AssignExpr {
277 span: DUMMY_SP,
278 left: MemberExpr {
279 span: DUMMY_SP,
280 obj: Box::new(env.clone().into()),
281 prop: MemberProp::Ident(quote_ident!("hasError")),
282 }
283 .into(),
284 op: op!("="),
285 right: Box::new(Expr::Lit(Lit::Bool(Bool {
286 span: DUMMY_SP,
287 value: true,
288 }))),
289 }
290 .into_stmt(),
291 ],
292 ..Default::default()
293 },
294 };
295
296 let finally_block = if is_async {
297 let result = private_ident!("result");
303
304 let var_decl = Stmt::Decl(Decl::Var(Box::new(VarDecl {
305 kind: VarDeclKind::Const,
306 decls: vec![VarDeclarator {
307 span: DUMMY_SP,
308 name: Pat::Ident(result.clone().into()),
309 init: Some(
310 CallExpr {
311 callee: helper!(ts, ts_dispose_resources),
312 args: vec![env.clone().as_arg()],
313 ..Default::default()
314 }
315 .into(),
316 ),
317 definite: false,
318 }],
319 ..Default::default()
320 })));
321 let if_stmt = Stmt::If(IfStmt {
322 span: DUMMY_SP,
323 test: result.clone().into(),
324 cons: Stmt::Expr(ExprStmt {
327 expr: Box::new(Expr::Await(AwaitExpr {
328 arg: result.clone().into(),
329 ..Default::default()
330 })),
331 ..Default::default()
332 })
333 .into(),
334 ..Default::default()
335 });
336
337 vec![var_decl, if_stmt]
338 } else {
339 vec![CallExpr {
342 callee: helper!(ts, ts_dispose_resources),
343 args: vec![env.clone().as_arg()],
344 ..Default::default()
345 }
346 .into_stmt()]
347 };
348
349 let try_stmt = TryStmt {
350 span: DUMMY_SP,
351 block: try_block,
352 handler: Some(catch_clause),
353 finalizer: Some(BlockStmt {
354 stmts: finally_block,
355 ..Default::default()
356 }),
357 };
358
359 new.push(T::from(try_stmt.into()));
360 new.extend(extras);
361
362 *stmts = new;
363 }
364}
365
366impl VisitMut for ExplicitResourceManagement {
367 noop_visit_mut_type!();
368
369 fn visit_mut_expr(&mut self, n: &mut Expr) {
370 maybe_grow_default(|| n.visit_mut_children_with(self));
371 }
372
373 fn visit_mut_for_of_stmt(&mut self, n: &mut ForOfStmt) {
374 n.visit_mut_children_with(self);
375
376 if let ForHead::UsingDecl(using) = &mut n.left {
377 let mut state = State::default();
378 state.has_await |= using.is_await;
379
380 let mut inner_var_decl = VarDecl {
381 kind: VarDeclKind::Const,
382 ..Default::default()
383 };
384
385 for decl in &mut using.decls {
386 let new_var = private_ident!("_");
387
388 inner_var_decl.decls.push(VarDeclarator {
389 span: DUMMY_SP,
390 name: decl.name.take(),
391 init: Some(
392 CallExpr {
393 callee: helper!(ts, ts_add_disposable_resource),
394 args: vec![
395 state.env.clone().as_arg(),
396 new_var.clone().as_arg(),
397 using.is_await.as_arg(),
398 ],
399 ..Default::default()
400 }
401 .into(),
402 ),
403 definite: false,
404 });
405
406 decl.name = Pat::Ident(new_var.clone().into());
407 }
408
409 let var_decl = Box::new(VarDecl {
410 span: DUMMY_SP,
411 kind: VarDeclKind::Const,
412 declare: false,
413 decls: using.decls.take(),
414 ..Default::default()
415 });
416
417 let mut body = vec![
418 Stmt::Decl(Decl::Var(Box::new(inner_var_decl))),
419 *n.body.take(),
420 ];
421 self.wrap_with_try(state, &mut body);
422
423 n.left = ForHead::VarDecl(var_decl);
424 n.body = Box::new(
425 BlockStmt {
426 span: DUMMY_SP,
427 stmts: body,
428 ..Default::default()
429 }
430 .into(),
431 )
432 }
433 }
434
435 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
436 self.visit_mut_stmt_likes(stmts)
437 }
438
439 fn visit_mut_stmt(&mut self, s: &mut Stmt) {
440 maybe_grow_default(|| s.visit_mut_children_with(self));
441
442 if let Stmt::Decl(Decl::Using(using)) = s {
443 let state = self.state.get_or_insert_with(Default::default);
444
445 let decl = handle_using_decl(using, state);
446 if decl.decls.is_empty() {
447 s.take();
448 return;
449 }
450
451 *s = Stmt::Decl(Decl::Var(decl));
452 }
453 }
454
455 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
456 let old_is_not_top_level = self.is_not_top_level;
457 self.is_not_top_level = true;
458 self.visit_mut_stmt_likes(stmts);
459 self.is_not_top_level = old_is_not_top_level;
460 }
461}
462
463fn handle_using_decl(using: &mut UsingDecl, state: &mut State) -> Box<VarDecl> {
464 state.has_await |= using.is_await;
465
466 for decl in &mut using.decls {
467 decl.init = Some(
468 CallExpr {
469 callee: helper!(ts, ts_add_disposable_resource),
470 args: vec![
471 state.env.clone().as_arg(),
472 decl.init.take().unwrap().as_arg(),
473 using.is_await.as_arg(),
474 ],
475 ..Default::default()
476 }
477 .into(),
478 );
479 }
480
481 Box::new(VarDecl {
482 span: DUMMY_SP,
483 kind: VarDeclKind::Const,
484 declare: false,
485 decls: using.decls.take(),
486 ..Default::default()
487 })
488}