turbopack_ecmascript/references/
unreachable.rs

1use std::mem::take;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use swc_core::{
6    atoms::{Atom, 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, Ident, KeyValuePatProp, Lit, ObjectPat, ObjectPatProp,
17            Pat, RestPat, Stmt, Str, SwitchCase, VarDecl, VarDeclKind, VarDeclarator,
18        },
19        visit::{
20            AstParentKind, VisitMut, VisitMutWith,
21            fields::{BlockStmtField, SwitchCaseField},
22        },
23    },
24    quote,
25};
26use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
27use turbopack_core::chunk::ChunkingContext;
28
29use crate::{
30    code_gen::{AstModifier, CodeGen, CodeGeneration},
31    utils::AstPathRange,
32};
33
34#[derive(
35    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
36)]
37pub struct Unreachable {
38    range: AstPathRange,
39}
40
41fn unreachable_atom() -> Atom {
42    atom!("TURBOPACK unreachable")
43}
44struct UnreachableModifier {
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: unreachable_atom().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: unreachable_atom(),
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    comments: SwcComments,
95    start_index: usize,
96}
97
98impl AstModifier for UnreachableRangeModifier {
99    fn visit_mut_block_stmt(&self, block: &mut BlockStmt) {
100        self.replace(&mut block.stmts, self.start_index);
101    }
102
103    fn visit_mut_switch_case(&self, case: &mut SwitchCase) {
104        self.replace(&mut case.cons, self.start_index);
105    }
106}
107
108impl UnreachableRangeModifier {
109    fn replace(&self, stmts: &mut Vec<Stmt>, start_index: usize) {
110        if stmts.len() > start_index + 1 {
111            let span = Span::dummy_with_cmt();
112
113            self.comments.add_leading(
114                span.lo,
115                Comment {
116                    kind: CommentKind::Line,
117                    span: DUMMY_SP,
118                    text: unreachable_atom(),
119                },
120            );
121
122            let unreachable_stmt = Stmt::Empty(EmptyStmt { span });
123
124            let unreachable = stmts
125                .splice(start_index + 1.., [unreachable_stmt])
126                .collect::<Vec<_>>();
127            for mut stmt in unreachable {
128                stmt.visit_mut_with(&mut ExtractDeclarations {
129                    stmts,
130                    in_nested_block_scope: false,
131                });
132            }
133        }
134    }
135}
136
137impl Unreachable {
138    pub fn new(range: AstPathRange) -> Self {
139        Unreachable { range }
140    }
141
142    pub async fn code_generation(
143        &self,
144        _chunking_context: Vc<Box<dyn ChunkingContext>>,
145    ) -> Result<CodeGeneration> {
146        let comments = SwcComments::default();
147
148        let visitors = match &self.range {
149            AstPathRange::Exact(path) => vec![(
150                path.clone(),
151                Box::new(UnreachableModifier {
152                    comments: comments.clone(),
153                }) as Box<dyn AstModifier>,
154            )],
155            AstPathRange::StartAfter(path) => {
156                let mut parent = &path[..];
157                while !parent.is_empty()
158                    && !matches!(parent.last().unwrap(), AstParentKind::Stmt(_))
159                {
160                    parent = &parent[0..parent.len() - 1];
161                }
162                if !parent.is_empty() {
163                    parent = &parent[0..parent.len() - 1];
164
165                    let (parent, [last]) = parent.split_at(parent.len() - 1) else {
166                        unreachable!();
167                    };
168                    if let &AstParentKind::BlockStmt(BlockStmtField::Stmts(start_index)) = last {
169                        vec![(
170                            parent.to_vec(),
171                            Box::new(UnreachableRangeModifier {
172                                comments: comments.clone(),
173                                start_index,
174                            }) as Box<dyn AstModifier>,
175                        )]
176                    } else if let &AstParentKind::SwitchCase(SwitchCaseField::Cons(start_index)) =
177                        last
178                    {
179                        vec![(
180                            parent.to_vec(),
181                            Box::new(UnreachableRangeModifier {
182                                comments: comments.clone(),
183                                start_index,
184                            }) as Box<dyn AstModifier>,
185                        )]
186                    } else {
187                        Vec::new()
188                    }
189                } else {
190                    Vec::new()
191                }
192            }
193        };
194
195        Ok(CodeGeneration::visitors_with_comments(visitors, comments))
196    }
197}
198
199impl From<Unreachable> for CodeGen {
200    fn from(val: Unreachable) -> Self {
201        CodeGen::Unreachable(val)
202    }
203}
204
205struct ExtractDeclarations<'a> {
206    stmts: &'a mut Vec<Stmt>,
207    in_nested_block_scope: bool,
208}
209
210impl VisitMut for ExtractDeclarations<'_> {
211    fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
212        let VarDecl {
213            span,
214            kind,
215            declare,
216            decls,
217            ctxt,
218        } = decl;
219        if self.in_nested_block_scope && !matches!(kind, VarDeclKind::Var) {
220            return;
221        }
222        let mut idents = Vec::new();
223        for decl in take(decls) {
224            collect_idents(&decl.name, &mut idents);
225        }
226        let decls = idents
227            .into_iter()
228            .map(|ident| VarDeclarator {
229                span: ident.span,
230                name: Pat::Ident(BindingIdent {
231                    id: ident,
232                    type_ann: None,
233                }),
234                init: if matches!(kind, VarDeclKind::Const) {
235                    Some(quote!("undefined" as Box<Expr>))
236                } else {
237                    None
238                },
239                definite: false,
240            })
241            .collect();
242        self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
243            span: *span,
244            kind: *kind,
245            declare: *declare,
246            ctxt: *ctxt,
247            decls,
248        }))));
249    }
250
251    fn visit_mut_fn_decl(&mut self, decl: &mut FnDecl) {
252        let FnDecl {
253            declare,
254            ident,
255            function,
256        } = decl;
257        self.stmts.push(Stmt::Decl(Decl::Fn(FnDecl {
258            declare: *declare,
259            ident: ident.take(),
260            function: function.take(),
261        })));
262    }
263
264    fn visit_mut_constructor(&mut self, _: &mut swc_core::ecma::ast::Constructor) {
265        // Do not walk into constructors
266    }
267
268    fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
269        // Do not walk into functions
270    }
271
272    fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
273        // Do not walk into getter properties
274    }
275
276    fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
277        // Do not walk into setter properties
278    }
279
280    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
281        // Do not walk into arrow expressions
282    }
283
284    fn visit_mut_class_decl(&mut self, decl: &mut ClassDecl) {
285        let ClassDecl { declare, ident, .. } = decl;
286        self.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
287            span: ident.span,
288            declare: *declare,
289            decls: vec![VarDeclarator {
290                span: ident.span,
291                name: Pat::Ident(BindingIdent {
292                    type_ann: None,
293                    id: ident.clone(),
294                }),
295                init: None,
296                definite: false,
297            }],
298            kind: VarDeclKind::Let,
299            ..Default::default()
300        }))));
301    }
302
303    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
304        let old = self.in_nested_block_scope;
305        self.in_nested_block_scope = true;
306        n.visit_mut_children_with(self);
307        self.in_nested_block_scope = old;
308    }
309}
310
311fn collect_idents(pat: &Pat, idents: &mut Vec<Ident>) {
312    match pat {
313        Pat::Ident(ident) => {
314            idents.push(ident.id.clone());
315        }
316        Pat::Array(ArrayPat { elems, .. }) => {
317            for elem in elems.iter() {
318                if let Some(elem) = elem.as_ref() {
319                    collect_idents(elem, idents);
320                }
321            }
322        }
323        Pat::Rest(RestPat { arg, .. }) => {
324            collect_idents(arg, idents);
325        }
326        Pat::Object(ObjectPat { props, .. }) => {
327            for prop in props.iter() {
328                match prop {
329                    ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
330                        collect_idents(value, idents);
331                    }
332                    ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
333                        idents.push(key.id.clone());
334                    }
335                    ObjectPatProp::Rest(RestPat { arg, .. }) => {
336                        collect_idents(arg, idents);
337                    }
338                }
339            }
340        }
341        Pat::Assign(AssignPat { left, .. }) => {
342            collect_idents(left, idents);
343        }
344        Pat::Invalid(_) | Pat::Expr(_) => {
345            // ignore
346        }
347    }
348}