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 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 }
267
268 fn visit_mut_function(&mut self, _: &mut swc_core::ecma::ast::Function) {
269 }
271
272 fn visit_mut_getter_prop(&mut self, _: &mut swc_core::ecma::ast::GetterProp) {
273 }
275
276 fn visit_mut_setter_prop(&mut self, _: &mut swc_core::ecma::ast::SetterProp) {
277 }
279
280 fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {
281 }
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 }
347 }
348}