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 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 }
266
267 fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
268 }
270
271 fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
272 }
274
275 fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
276 }
278
279 fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
280 }
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 }
346 }
347}