Skip to main content

turbopack_ecmascript/references/
removal.rs

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        // We use an AST node instead of a comment here because we need to replace it with a valid
52        // JS expression anyway.
53        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
143/// Removes the code at the given path and replaces it with a comment.
144impl 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        // Do not walk into constructors
293    }
294
295    fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
296        // Do not walk into functions
297    }
298
299    fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
300        // Do not walk into getter properties
301    }
302
303    fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
304        // Do not walk into setter properties
305    }
306
307    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
308        // Do not walk into arrow expressions
309    }
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            // ignore
373        }
374    }
375}