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, trace::TraceRawVcs};
29use turbopack_core::chunk::ChunkingContext;
30
31use crate::{
32 code_gen::{AstModifier, CodeGen, CodeGeneration},
33 utils::AstPathRange,
34};
35
36#[derive(
37 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
38)]
39pub struct RemovalCodeGen {
40 comment_replacement: RcStr,
41 range: AstPathRange,
42}
43
44struct UnreachableModifier {
45 comment_replacement: Atom,
46 comments: SwcComments,
47}
48
49impl AstModifier for UnreachableModifier {
50 fn visit_mut_expr(&self, node: &mut Expr) {
51 let span = node.span();
54
55 *node = Expr::Lit(Lit::Str(Str {
56 span,
57 value: self.comment_replacement.clone().into(),
58 raw: None,
59 }));
60 }
61
62 fn visit_mut_stmt(&self, stmt: &mut Stmt) {
63 let mut replacement = Vec::new();
64
65 let span = Span::dummy_with_cmt();
66
67 self.comments.add_leading(
68 span.lo,
69 Comment {
70 kind: CommentKind::Line,
71 span: DUMMY_SP,
72 text: self.comment_replacement.clone(),
73 },
74 );
75
76 stmt.visit_mut_with(&mut ExtractDeclarations {
77 stmts: &mut replacement,
78 in_nested_block_scope: false,
79 });
80
81 if replacement.is_empty() {
82 *stmt = Stmt::Empty(EmptyStmt { span });
83 return;
84 }
85
86 *stmt = Stmt::Block(BlockStmt {
87 span,
88 stmts: replacement,
89 ..Default::default()
90 });
91 }
92}
93
94struct UnreachableRangeModifier {
95 comment_replacement: Atom,
96 comments: SwcComments,
97 start_index: usize,
98}
99
100impl AstModifier for UnreachableRangeModifier {
101 fn visit_mut_block_stmt(&self, block: &mut BlockStmt) {
102 self.replace(&mut block.stmts, self.start_index);
103 }
104
105 fn visit_mut_function_body(&self, body: &mut FunctionBody) {
106 self.replace(&mut body.stmts, self.start_index);
107 }
108
109 fn visit_mut_switch_case(&self, case: &mut SwitchCase) {
110 self.replace(&mut case.cons, self.start_index);
111 }
112}
113
114impl UnreachableRangeModifier {
115 fn replace(&self, stmts: &mut Vec<Stmt>, start_index: usize) {
116 if stmts.len() > start_index + 1 {
117 let span = Span::dummy_with_cmt();
118
119 self.comments.add_leading(
120 span.lo,
121 Comment {
122 kind: CommentKind::Line,
123 span: DUMMY_SP,
124 text: self.comment_replacement.clone(),
125 },
126 );
127
128 let unreachable_stmt = Stmt::Empty(EmptyStmt { span });
129
130 let unreachable = stmts
131 .splice(start_index + 1.., [unreachable_stmt])
132 .collect::<Vec<_>>();
133 for mut stmt in unreachable {
134 stmt.visit_mut_with(&mut ExtractDeclarations {
135 stmts,
136 in_nested_block_scope: false,
137 });
138 }
139 }
140 }
141}
142
143impl RemovalCodeGen {
145 pub fn new(comment_replacement: RcStr, range: AstPathRange) -> Self {
146 RemovalCodeGen {
147 comment_replacement,
148 range,
149 }
150 }
151
152 pub async fn code_generation(
153 &self,
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.clone(),
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 mut parent = &path[..];
170 while !parent.is_empty()
171 && !matches!(parent.last().unwrap(), AstParentKind::Stmt(_))
172 {
173 parent = &parent[0..parent.len() - 1];
174 }
175 if !parent.is_empty() {
176 parent = &parent[0..parent.len() - 1];
177
178 let (parent, [last]) = parent.split_at(parent.len() - 1) else {
179 unreachable!();
180 };
181 if let &AstParentKind::BlockStmt(BlockStmtField::Stmts(start_index)) = last {
182 vec![(
183 parent.to_vec(),
184 Box::new(UnreachableRangeModifier {
185 comment_replacement: comment_replacement.clone(),
186 comments: comments.clone(),
187 start_index,
188 }) as Box<dyn AstModifier>,
189 )]
190 } else if let &AstParentKind::FunctionBody(FunctionBodyField::Stmts(
191 start_index,
192 )) = last
193 {
194 vec![(
195 parent.to_vec(),
196 Box::new(UnreachableRangeModifier {
197 comment_replacement: comment_replacement.clone(),
198 comments: comments.clone(),
199 start_index,
200 }) as Box<dyn AstModifier>,
201 )]
202 } else if let &AstParentKind::SwitchCase(SwitchCaseField::Cons(start_index)) =
203 last
204 {
205 vec![(
206 parent.to_vec(),
207 Box::new(UnreachableRangeModifier {
208 comment_replacement: comment_replacement.clone(),
209 comments: comments.clone(),
210 start_index,
211 }) as Box<dyn AstModifier>,
212 )]
213 } else {
214 Vec::new()
215 }
216 } else {
217 Vec::new()
218 }
219 }
220 };
221
222 Ok(CodeGeneration::visitors_with_comments(visitors, comments))
223 }
224}
225
226impl From<RemovalCodeGen> for CodeGen {
227 fn from(val: RemovalCodeGen) -> Self {
228 CodeGen::RemovalCodeGen(val)
229 }
230}
231
232struct ExtractDeclarations<'a> {
233 stmts: &'a mut Vec<Stmt>,
234 in_nested_block_scope: bool,
235}
236
237impl VisitMut for ExtractDeclarations<'_> {
238 fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
239 let VarDecl {
240 span,
241 kind,
242 declare,
243 decls,
244 ctxt,
245 } = decl;
246 if self.in_nested_block_scope && !matches!(kind, VarDeclKind::Var) {
247 return;
248 }
249 let mut idents = Vec::new();
250 for decl in take(decls) {
251 collect_idents(&decl.name, &mut idents);
252 }
253 let decls = idents
254 .into_iter()
255 .map(|ident| VarDeclarator {
256 span: ident.span,
257 name: Pat::Ident(BindingIdent {
258 id: ident,
259 type_ann: None,
260 }),
261 init: if matches!(kind, VarDeclKind::Const) {
262 Some(quote!("undefined" as Box<Expr>))
263 } else {
264 None
265 },
266 definite: false,
267 })
268 .collect();
269 self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
270 span: *span,
271 kind: *kind,
272 declare: *declare,
273 ctxt: *ctxt,
274 decls,
275 }))));
276 }
277
278 fn visit_mut_fn_decl(&mut self, decl: &mut FnDecl) {
279 let FnDecl {
280 declare,
281 ident,
282 function,
283 } = decl;
284 self.stmts.push(Stmt::Decl(Decl::Fn(FnDecl {
285 declare: *declare,
286 ident: ident.take(),
287 function: function.take(),
288 })));
289 }
290
291 fn visit_mut_constructor(&mut self, _: &mut swc_core::ecma::ast::Constructor) {
292 }
294
295 fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
296 }
298
299 fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
300 }
302
303 fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
304 }
306
307 fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
308 }
310
311 fn visit_mut_class_decl(&mut self, decl: &mut ClassDecl) {
312 let ClassDecl { declare, ident, .. } = decl;
313 self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
314 span: ident.span,
315 declare: *declare,
316 decls: vec![VarDeclarator {
317 span: ident.span,
318 name: Pat::Ident(BindingIdent {
319 type_ann: None,
320 id: ident.clone(),
321 }),
322 init: None,
323 definite: false,
324 }],
325 kind: VarDeclKind::Let,
326 ..Default::default()
327 }))));
328 }
329
330 fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
331 let old = self.in_nested_block_scope;
332 self.in_nested_block_scope = true;
333 n.visit_mut_children_with(self);
334 self.in_nested_block_scope = old;
335 }
336}
337
338fn collect_idents(pat: &Pat, idents: &mut Vec<Ident>) {
339 match pat {
340 Pat::Ident(ident) => {
341 idents.push(ident.id.clone());
342 }
343 Pat::Array(ArrayPat { elems, .. }) => {
344 for elem in elems.iter() {
345 if let Some(elem) = elem.as_ref() {
346 collect_idents(elem, idents);
347 }
348 }
349 }
350 Pat::Rest(RestPat { arg, .. }) => {
351 collect_idents(arg, idents);
352 }
353 Pat::Object(ObjectPat { props, .. }) => {
354 for prop in props.iter() {
355 match prop {
356 ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
357 collect_idents(value, idents);
358 }
359 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
360 idents.push(key.id.clone());
361 }
362 ObjectPatProp::Rest(RestPat { arg, .. }) => {
363 collect_idents(arg, idents);
364 }
365 }
366 }
367 }
368 Pat::Assign(AssignPat { left, .. }) => {
369 collect_idents(left, idents);
370 }
371 Pat::Invalid(_) | Pat::Expr(_) => {
372 }
374 }
375}