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};
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        // We use an AST node instead of a comment here because we need to replace it with a valid
51        // JS expression anyway.
52        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
142/// Removes the code at the given path and replaces it with a comment.
143impl 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                // Walk up to the enclosing statement, then past the element naming the slot
170                // it occupies, leaving the node that owns the statement list plus the index
171                // to start removing from. Every ancestor of an interned path is itself a
172                // node, so this only walks the trie.
173                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                // `slot` names which statement of its parent this is, e.g. `Stmts(3)`.
178                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                    // Not a statement list we know how to truncate.
186                    _ => {
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        // Do not walk into constructors
273    }
274
275    fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
276        // Do not walk into functions
277    }
278
279    fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
280        // Do not walk into getter properties
281    }
282
283    fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
284        // Do not walk into setter properties
285    }
286
287    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
288        // Do not walk into arrow expressions
289    }
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            // ignore
353        }
354    }
355}