1use std::mem::take;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use swc_core::{
6 atoms::Atom,
7 base::SwcComments,
8 common::{
9 DUMMY_SP, Span, Spanned,
10 comments::{Comment, CommentKind, Comments},
11 util::take::Take,
12 },
13 ecma::{
14 ast::{
15 ArrayPat, ArrowExpr, AssignPat, AssignPatProp, BindingIdent, BlockStmt, ClassDecl,
16 Decl, EmptyStmt, Expr, FnDecl, FunctionBody, Ident, KeyValuePatProp, Lit, ObjectPat,
17 ObjectPatProp, Pat, RestPat, Stmt, Str, SwitchCase, VarDecl, VarDeclKind,
18 VarDeclarator,
19 },
20 visit::{
21 AstParentKind, VisitMut, VisitMutWith,
22 fields::{BlockStmtField, FunctionBodyField, SwitchCaseField},
23 },
24 },
25 quote,
26};
27use turbo_rcstr::RcStr;
28use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat};
29use turbopack_core::chunk::ChunkingContext;
30
31use crate::{
32 ast_path_trie::{AstPathId, AstPathTrie},
33 code_gen::{AstModifier, CodeGen, CodeGeneration},
34 utils::AstPathRange,
35};
36
37#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode)]
38pub struct RemovalCodeGen {
39 comment_replacement: RcStr,
40 range: AstPathRange,
41}
42
43struct UnreachableModifier {
44 comment_replacement: Atom,
45 comments: SwcComments,
46}
47
48impl AstModifier for UnreachableModifier {
49 fn visit_mut_expr(&self, node: &mut Expr) {
50 let span = node.span();
53
54 *node = Expr::Lit(Lit::Str(Str {
55 span,
56 value: self.comment_replacement.clone().into(),
57 raw: None,
58 }));
59 }
60
61 fn visit_mut_stmt(&self, stmt: &mut Stmt) {
62 let mut replacement = Vec::new();
63
64 let span = Span::dummy_with_cmt();
65
66 self.comments.add_leading(
67 span.lo,
68 Comment {
69 kind: CommentKind::Line,
70 span: DUMMY_SP,
71 text: self.comment_replacement.clone(),
72 },
73 );
74
75 stmt.visit_mut_with(&mut ExtractDeclarations {
76 stmts: &mut replacement,
77 in_nested_block_scope: false,
78 });
79
80 if replacement.is_empty() {
81 *stmt = Stmt::Empty(EmptyStmt { span });
82 return;
83 }
84
85 *stmt = Stmt::Block(BlockStmt {
86 span,
87 stmts: replacement,
88 ..Default::default()
89 });
90 }
91}
92
93struct UnreachableRangeModifier {
94 comment_replacement: Atom,
95 comments: SwcComments,
96 start_index: usize,
97}
98
99impl AstModifier for UnreachableRangeModifier {
100 fn visit_mut_block_stmt(&self, block: &mut BlockStmt) {
101 self.replace(&mut block.stmts, self.start_index);
102 }
103
104 fn visit_mut_function_body(&self, body: &mut FunctionBody) {
105 self.replace(&mut body.stmts, self.start_index);
106 }
107
108 fn visit_mut_switch_case(&self, case: &mut SwitchCase) {
109 self.replace(&mut case.cons, self.start_index);
110 }
111}
112
113impl UnreachableRangeModifier {
114 fn replace(&self, stmts: &mut Vec<Stmt>, start_index: usize) {
115 if stmts.len() > start_index + 1 {
116 let span = Span::dummy_with_cmt();
117
118 self.comments.add_leading(
119 span.lo,
120 Comment {
121 kind: CommentKind::Line,
122 span: DUMMY_SP,
123 text: self.comment_replacement.clone(),
124 },
125 );
126
127 let unreachable_stmt = Stmt::Empty(EmptyStmt { span });
128
129 let unreachable = stmts
130 .splice(start_index + 1.., [unreachable_stmt])
131 .collect::<Vec<_>>();
132 for mut stmt in unreachable {
133 stmt.visit_mut_with(&mut ExtractDeclarations {
134 stmts,
135 in_nested_block_scope: false,
136 });
137 }
138 }
139 }
140}
141
142impl RemovalCodeGen {
144 pub fn new(comment_replacement: RcStr, range: AstPathRange) -> Self {
145 RemovalCodeGen {
146 comment_replacement,
147 range,
148 }
149 }
150
151 pub async fn code_generation(
152 &self,
153 trie: &AstPathTrie,
154 _chunking_context: Vc<Box<dyn ChunkingContext>>,
155 ) -> Result<CodeGeneration> {
156 let comments = SwcComments::default();
157
158 let comment_replacement = Atom::from(self.comment_replacement.as_str());
159
160 let visitors = match &self.range {
161 AstPathRange::Exact(path) => vec![(
162 *path,
163 Box::new(UnreachableModifier {
164 comment_replacement: comment_replacement.clone(),
165 comments: comments.clone(),
166 }) as Box<dyn AstModifier>,
167 )],
168 AstPathRange::StartAfter(path) => {
169 let Some(stmt) = trie.find_last(*path, |k| matches!(k, AstParentKind::Stmt(_)))
174 else {
175 return Ok(CodeGeneration::visitors_with_comments(vec![], comments));
176 };
177 let Some(slot) = trie.get_parent(stmt) else {
179 return Ok(CodeGeneration::visitors_with_comments(vec![], comments));
180 };
181 let start_index = match trie.get(slot) {
182 Some(AstParentKind::BlockStmt(BlockStmtField::Stmts(i)))
183 | Some(AstParentKind::FunctionBody(FunctionBodyField::Stmts(i)))
184 | Some(AstParentKind::SwitchCase(SwitchCaseField::Cons(i))) => i,
185 _ => {
187 return Ok(CodeGeneration::visitors_with_comments(vec![], comments));
188 }
189 };
190 let owner = trie.get_parent(slot).unwrap_or(AstPathId::ROOT);
191 vec![(
192 owner,
193 Box::new(UnreachableRangeModifier {
194 comment_replacement: comment_replacement.clone(),
195 comments: comments.clone(),
196 start_index,
197 }) as Box<dyn AstModifier>,
198 )]
199 }
200 };
201
202 Ok(CodeGeneration::visitors_with_comments(visitors, comments))
203 }
204}
205
206impl From<RemovalCodeGen> for CodeGen {
207 fn from(val: RemovalCodeGen) -> Self {
208 CodeGen::RemovalCodeGen(val)
209 }
210}
211
212struct ExtractDeclarations<'a> {
213 stmts: &'a mut Vec<Stmt>,
214 in_nested_block_scope: bool,
215}
216
217impl VisitMut for ExtractDeclarations<'_> {
218 fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
219 let VarDecl {
220 span,
221 kind,
222 declare,
223 decls,
224 ctxt,
225 } = decl;
226 if self.in_nested_block_scope && !matches!(kind, VarDeclKind::Var) {
227 return;
228 }
229 let mut idents = Vec::new();
230 for decl in take(decls) {
231 collect_idents(&decl.name, &mut idents);
232 }
233 let decls = idents
234 .into_iter()
235 .map(|ident| VarDeclarator {
236 span: ident.span,
237 name: Pat::Ident(BindingIdent {
238 id: ident,
239 type_ann: None,
240 }),
241 init: if matches!(kind, VarDeclKind::Const) {
242 Some(quote!("undefined" as Box<Expr>))
243 } else {
244 None
245 },
246 definite: false,
247 })
248 .collect();
249 self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
250 span: *span,
251 kind: *kind,
252 declare: *declare,
253 ctxt: *ctxt,
254 decls,
255 }))));
256 }
257
258 fn visit_mut_fn_decl(&mut self, decl: &mut FnDecl) {
259 let FnDecl {
260 declare,
261 ident,
262 function,
263 } = decl;
264 self.stmts.push(Stmt::Decl(Decl::Fn(FnDecl {
265 declare: *declare,
266 ident: ident.take(),
267 function: function.take(),
268 })));
269 }
270
271 fn visit_mut_constructor(&mut self, _: &mut swc_core::ecma::ast::Constructor) {
272 }
274
275 fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
276 }
278
279 fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
280 }
282
283 fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
284 }
286
287 fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
288 }
290
291 fn visit_mut_class_decl(&mut self, decl: &mut ClassDecl) {
292 let ClassDecl { declare, ident, .. } = decl;
293 self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
294 span: ident.span,
295 declare: *declare,
296 decls: vec![VarDeclarator {
297 span: ident.span,
298 name: Pat::Ident(BindingIdent {
299 type_ann: None,
300 id: ident.clone(),
301 }),
302 init: None,
303 definite: false,
304 }],
305 kind: VarDeclKind::Let,
306 ..Default::default()
307 }))));
308 }
309
310 fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
311 let old = self.in_nested_block_scope;
312 self.in_nested_block_scope = true;
313 n.visit_mut_children_with(self);
314 self.in_nested_block_scope = old;
315 }
316}
317
318fn collect_idents(pat: &Pat, idents: &mut Vec<Ident>) {
319 match pat {
320 Pat::Ident(ident) => {
321 idents.push(ident.id.clone());
322 }
323 Pat::Array(ArrayPat { elems, .. }) => {
324 for elem in elems.iter() {
325 if let Some(elem) = elem.as_ref() {
326 collect_idents(elem, idents);
327 }
328 }
329 }
330 Pat::Rest(RestPat { arg, .. }) => {
331 collect_idents(arg, idents);
332 }
333 Pat::Object(ObjectPat { props, .. }) => {
334 for prop in props.iter() {
335 match prop {
336 ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
337 collect_idents(value, idents);
338 }
339 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
340 idents.push(key.id.clone());
341 }
342 ObjectPatProp::Rest(RestPat { arg, .. }) => {
343 collect_idents(arg, idents);
344 }
345 }
346 }
347 }
348 Pat::Assign(AssignPat { left, .. }) => {
349 collect_idents(left, idents);
350 }
351 Pat::Invalid(_) | Pat::Expr(_) => {
352 }
354 }
355}