turbopack_ecmascript/references/
unreachable.rs

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