turbopack_ecmascript/references/
unreachable.rs

1use std::mem::take;
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use swc_core::{
6    common::{Spanned, util::take::Take},
7    ecma::{
8        ast::{
9            ArrayPat, ArrowExpr, AssignPat, AssignPatProp, BindingIdent, BlockStmt, ClassDecl,
10            Decl, FnDecl, Ident, KeyValuePatProp, ObjectPat, ObjectPatProp, Pat, RestPat, Stmt,
11            VarDecl, VarDeclKind, VarDeclarator,
12        },
13        visit::{
14            AstParentKind, VisitMut, VisitMutWith,
15            fields::{BlockStmtField, SwitchCaseField},
16        },
17    },
18    quote,
19};
20use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
21use turbopack_core::{chunk::ChunkingContext, module_graph::ModuleGraph};
22
23use crate::{
24    code_gen::{CodeGen, CodeGeneration},
25    create_visitor,
26    utils::AstPathRange,
27};
28
29#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
30
31pub struct Unreachable {
32    range: AstPathRange,
33}
34
35impl Unreachable {
36    pub fn new(range: AstPathRange) -> Self {
37        Unreachable { range }
38    }
39
40    pub async fn code_generation(
41        &self,
42        _module_graph: Vc<ModuleGraph>,
43        _chunking_context: Vc<Box<dyn ChunkingContext>>,
44    ) -> Result<CodeGeneration> {
45        let visitors = match &self.range {
46            AstPathRange::Exact(path) => {
47                [
48                    // Unreachable might be used on Stmt or Expr
49                    create_visitor!(exact path, visit_mut_expr(expr: &mut Expr) {
50                        *expr = quote!("(\"TURBOPACK unreachable\", undefined)" as Expr);
51                    }),
52                    create_visitor!(exact path, visit_mut_stmt(stmt: &mut Stmt) {
53                        // TODO(WEB-553) walk ast to find all `var` declarations and keep them
54                        // since they hoist out of the scope
55                        let mut replacement = Vec::new();
56                        replacement.push(quote!("\"TURBOPACK unreachable\";" as Stmt));
57                        stmt.visit_mut_with(&mut ExtractDeclarations {
58                            stmts: &mut replacement,
59                            in_nested_block_scope: false,
60                        });
61                        *stmt = Stmt::Block(BlockStmt {
62                            span: stmt.span(),
63                            stmts: replacement,
64                            ..Default::default()
65                        });
66                    }),
67                ]
68                .into()
69            }
70            AstPathRange::StartAfter(path) => {
71                let mut parent = &path[..];
72                while !parent.is_empty()
73                    && !matches!(parent.last().unwrap(), AstParentKind::Stmt(_))
74                {
75                    parent = &parent[0..parent.len() - 1];
76                }
77                if !parent.is_empty() {
78                    parent = &parent[0..parent.len() - 1];
79                    fn replace(stmts: &mut Vec<Stmt>, start_index: usize) {
80                        if stmts.len() > start_index + 1 {
81                            let unreachable = stmts
82                                .splice(
83                                    start_index + 1..,
84                                    [quote!("\"TURBOPACK unreachable\";" as Stmt)].into_iter(),
85                                )
86                                .collect::<Vec<_>>();
87                            for mut stmt in unreachable {
88                                stmt.visit_mut_with(&mut ExtractDeclarations {
89                                    stmts,
90                                    in_nested_block_scope: false,
91                                });
92                            }
93                        }
94                    }
95                    let (parent, [last]) = parent.split_at(parent.len() - 1) else {
96                        unreachable!();
97                    };
98                    if let &AstParentKind::BlockStmt(BlockStmtField::Stmts(start_index)) = last {
99                        [
100                            create_visitor!(exact parent, visit_mut_block_stmt(block: &mut BlockStmt) {
101                                replace(&mut block.stmts, start_index);
102                            }),
103                        ]
104                        .into()
105                    } else if let &AstParentKind::SwitchCase(SwitchCaseField::Cons(start_index)) =
106                        last
107                    {
108                        [
109                            create_visitor!(exact parent, visit_mut_switch_case(case: &mut SwitchCase) {
110                                replace(&mut case.cons, start_index);
111                            }),
112                        ]
113                        .into()
114                    } else {
115                        Vec::new()
116                    }
117                } else {
118                    Vec::new()
119                }
120            }
121        };
122
123        Ok(CodeGeneration::visitors(visitors))
124    }
125}
126
127impl From<Unreachable> for CodeGen {
128    fn from(val: Unreachable) -> Self {
129        CodeGen::Unreachable(val)
130    }
131}
132
133struct ExtractDeclarations<'a> {
134    stmts: &'a mut Vec<Stmt>,
135    in_nested_block_scope: bool,
136}
137
138impl VisitMut for ExtractDeclarations<'_> {
139    fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
140        let VarDecl {
141            span,
142            kind,
143            declare,
144            decls,
145            ctxt,
146        } = decl;
147        if self.in_nested_block_scope && !matches!(kind, VarDeclKind::Var) {
148            return;
149        }
150        let mut idents = Vec::new();
151        for decl in take(decls) {
152            collect_idents(&decl.name, &mut idents);
153        }
154        let decls = idents
155            .into_iter()
156            .map(|ident| VarDeclarator {
157                span: ident.span,
158                name: Pat::Ident(BindingIdent {
159                    id: ident,
160                    type_ann: None,
161                }),
162                init: if matches!(kind, VarDeclKind::Const) {
163                    Some(quote!("undefined" as Box<Expr>))
164                } else {
165                    None
166                },
167                definite: false,
168            })
169            .collect();
170        self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
171            span: *span,
172            kind: *kind,
173            declare: *declare,
174            ctxt: *ctxt,
175            decls,
176        }))));
177    }
178
179    fn visit_mut_fn_decl(&mut self, decl: &mut FnDecl) {
180        let FnDecl {
181            declare,
182            ident,
183            function,
184        } = decl;
185        self.stmts.push(Stmt::Decl(Decl::Fn(FnDecl {
186            declare: *declare,
187            ident: ident.take(),
188            function: function.take(),
189        })));
190    }
191
192    fn visit_mut_constructor(&mut self, _: &mut swc_core::ecma::ast::Constructor) {
193        // Do not walk into constructors
194    }
195
196    fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
197        // Do not walk into functions
198    }
199
200    fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
201        // Do not walk into getter properties
202    }
203
204    fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
205        // Do not walk into setter properties
206    }
207
208    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
209        // Do not walk into arrow expressions
210    }
211
212    fn visit_mut_class_decl(&mut self, decl: &mut ClassDecl) {
213        let ClassDecl { declare, ident, .. } = decl;
214        self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
215            span: ident.span,
216            declare: *declare,
217            decls: vec![VarDeclarator {
218                span: ident.span,
219                name: Pat::Ident(BindingIdent {
220                    type_ann: None,
221                    id: ident.clone(),
222                }),
223                init: None,
224                definite: false,
225            }],
226            kind: VarDeclKind::Let,
227            ..Default::default()
228        }))));
229    }
230
231    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
232        let old = self.in_nested_block_scope;
233        self.in_nested_block_scope = true;
234        n.visit_mut_children_with(self);
235        self.in_nested_block_scope = old;
236    }
237}
238
239fn collect_idents(pat: &Pat, idents: &mut Vec<Ident>) {
240    match pat {
241        Pat::Ident(ident) => {
242            idents.push(ident.id.clone());
243        }
244        Pat::Array(ArrayPat { elems, .. }) => {
245            for elem in elems.iter() {
246                if let Some(elem) = elem.as_ref() {
247                    collect_idents(elem, idents);
248                }
249            }
250        }
251        Pat::Rest(RestPat { arg, .. }) => {
252            collect_idents(arg, idents);
253        }
254        Pat::Object(ObjectPat { props, .. }) => {
255            for prop in props.iter() {
256                match prop {
257                    ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
258                        collect_idents(value, idents);
259                    }
260                    ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
261                        idents.push(key.id.clone());
262                    }
263                    ObjectPatProp::Rest(RestPat { arg, .. }) => {
264                        collect_idents(arg, idents);
265                    }
266                }
267            }
268        }
269        Pat::Assign(AssignPat { left, .. }) => {
270            collect_idents(left, idents);
271        }
272        Pat::Invalid(_) | Pat::Expr(_) => {
273            // ignore
274        }
275    }
276}