turbopack_ecmascript/
code_gen.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use swc_core::{
4    base::SwcComments,
5    ecma::{
6        ast::{
7            BlockStmt, CallExpr, Expr, Lit, MemberExpr, ModuleDecl, ModuleItem, Pat, Program, Prop,
8            SimpleAssignTarget, Stmt, Str, SwitchCase,
9        },
10        visit::AstParentKind,
11    },
12};
13use turbo_rcstr::RcStr;
14use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
15use turbopack_core::{chunk::ChunkingContext, reference::ModuleReference};
16
17use crate::{
18    ScopeHoistingContext,
19    references::{
20        AstPath,
21        amd::AmdDefineWithDependenciesCodeGen,
22        cjs::{
23            CjsRequireAssetReferenceCodeGen, CjsRequireCacheAccess,
24            CjsRequireResolveAssetReferenceCodeGen,
25        },
26        constant_condition::ConstantConditionCodeGen,
27        constant_value::ConstantValueCodeGen,
28        dynamic_expression::DynamicExpression,
29        esm::{
30            EsmBinding, EsmModuleItem, ImportMetaBinding, ImportMetaRef,
31            dynamic::EsmAsyncAssetReferenceCodeGen, module_id::EsmModuleIdAssetReferenceCodeGen,
32            url::UrlAssetReferenceCodeGen,
33        },
34        ident::IdentReplacement,
35        member::MemberReplacement,
36        require_context::RequireContextAssetReferenceCodeGen,
37        unreachable::Unreachable,
38        worker::WorkerAssetReferenceCodeGen,
39    },
40};
41
42#[derive(Default)]
43pub struct CodeGeneration {
44    /// ast nodes matching the span will be visitor by the visitor
45    pub visitors: Vec<(Vec<AstParentKind>, Box<dyn AstModifier>)>,
46    pub hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
47    pub early_hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
48    pub late_stmts: Vec<CodeGenerationHoistedStmt>,
49    pub early_late_stmts: Vec<CodeGenerationHoistedStmt>,
50    pub comments: Option<SwcComments>,
51}
52
53impl CodeGeneration {
54    pub fn empty() -> Self {
55        CodeGeneration {
56            ..Default::default()
57        }
58    }
59
60    pub fn new(
61        visitors: Vec<(Vec<AstParentKind>, Box<dyn AstModifier>)>,
62        hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
63        early_hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
64        late_stmts: Vec<CodeGenerationHoistedStmt>,
65        early_late_stmts: Vec<CodeGenerationHoistedStmt>,
66    ) -> Self {
67        CodeGeneration {
68            visitors,
69            hoisted_stmts,
70            early_hoisted_stmts,
71            late_stmts,
72            early_late_stmts,
73            ..Default::default()
74        }
75    }
76
77    pub fn visitors_with_comments(
78        visitors: Vec<(Vec<AstParentKind>, Box<dyn AstModifier>)>,
79        comments: SwcComments,
80    ) -> Self {
81        CodeGeneration {
82            visitors,
83            comments: Some(comments),
84            ..Default::default()
85        }
86    }
87
88    pub fn visitors(visitors: Vec<(Vec<AstParentKind>, Box<dyn AstModifier>)>) -> Self {
89        CodeGeneration {
90            visitors,
91            ..Default::default()
92        }
93    }
94
95    pub fn hoisted_stmt(key: RcStr, stmt: Stmt) -> Self {
96        CodeGeneration {
97            hoisted_stmts: vec![CodeGenerationHoistedStmt::new(key, stmt)],
98            ..Default::default()
99        }
100    }
101
102    pub fn hoisted_stmts(hoisted_stmts: Vec<CodeGenerationHoistedStmt>) -> Self {
103        CodeGeneration {
104            hoisted_stmts,
105            ..Default::default()
106        }
107    }
108}
109
110#[derive(Clone)]
111pub struct CodeGenerationHoistedStmt {
112    pub key: RcStr,
113    pub stmt: Stmt,
114}
115
116impl CodeGenerationHoistedStmt {
117    pub fn new(key: RcStr, stmt: Stmt) -> Self {
118        CodeGenerationHoistedStmt { key, stmt }
119    }
120}
121
122macro_rules! method {
123    ($name:ident, $T:ty) => {
124        fn $name(&self, _node: &mut $T) {}
125    };
126}
127
128pub trait AstModifier: Send + Sync {
129    method!(visit_mut_prop, Prop);
130    method!(visit_mut_simple_assign_target, SimpleAssignTarget);
131    method!(visit_mut_expr, Expr);
132    method!(visit_mut_member_expr, MemberExpr);
133    method!(visit_mut_pat, Pat);
134    method!(visit_mut_stmt, Stmt);
135    method!(visit_mut_module_decl, ModuleDecl);
136    method!(visit_mut_module_item, ModuleItem);
137    method!(visit_mut_call_expr, CallExpr);
138    method!(visit_mut_lit, Lit);
139    method!(visit_mut_str, Str);
140    method!(visit_mut_block_stmt, BlockStmt);
141    method!(visit_mut_switch_case, SwitchCase);
142    method!(visit_mut_program, Program);
143}
144
145pub trait ModifiableAst {
146    fn modify(&mut self, modifier: &dyn AstModifier);
147}
148
149macro_rules! impl_modify {
150    ($visit_mut_name:ident, $T:ty) => {
151        impl ModifiableAst for $T {
152            fn modify(&mut self, modifier: &dyn AstModifier) {
153                modifier.$visit_mut_name(self)
154            }
155        }
156    };
157}
158
159impl_modify!(visit_mut_prop, Prop);
160impl_modify!(visit_mut_simple_assign_target, SimpleAssignTarget);
161impl_modify!(visit_mut_expr, Expr);
162impl_modify!(visit_mut_member_expr, MemberExpr);
163impl_modify!(visit_mut_pat, Pat);
164impl_modify!(visit_mut_stmt, Stmt);
165impl_modify!(visit_mut_module_decl, ModuleDecl);
166impl_modify!(visit_mut_module_item, ModuleItem);
167impl_modify!(visit_mut_call_expr, CallExpr);
168impl_modify!(visit_mut_lit, Lit);
169impl_modify!(visit_mut_str, Str);
170impl_modify!(visit_mut_block_stmt, BlockStmt);
171impl_modify!(visit_mut_switch_case, SwitchCase);
172impl_modify!(visit_mut_program, Program);
173
174#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
175pub enum CodeGen {
176    // AMD occurs very rarely and makes the enum much bigger
177    AmdDefineWithDependenciesCodeGen(Box<AmdDefineWithDependenciesCodeGen>),
178    CjsRequireCacheAccess(CjsRequireCacheAccess),
179    ConstantConditionCodeGen(ConstantConditionCodeGen),
180    ConstantValueCodeGen(ConstantValueCodeGen),
181    DynamicExpression(DynamicExpression),
182    EsmBinding(EsmBinding),
183    EsmModuleItem(EsmModuleItem),
184    IdentReplacement(IdentReplacement),
185    ImportMetaBinding(ImportMetaBinding),
186    ImportMetaRef(ImportMetaRef),
187    MemberReplacement(MemberReplacement),
188    Unreachable(Unreachable),
189    CjsRequireAssetReferenceCodeGen(CjsRequireAssetReferenceCodeGen),
190    CjsRequireResolveAssetReferenceCodeGen(CjsRequireResolveAssetReferenceCodeGen),
191    EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen),
192    EsmModuleIdAssetReferenceCodeGen(EsmModuleIdAssetReferenceCodeGen),
193    RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen),
194    UrlAssetReferenceCodeGen(UrlAssetReferenceCodeGen),
195    WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen),
196}
197
198impl CodeGen {
199    pub async fn code_generation(
200        &self,
201        ctx: Vc<Box<dyn ChunkingContext>>,
202        scope_hoisting_context: ScopeHoistingContext<'_>,
203    ) -> Result<CodeGeneration> {
204        match self {
205            Self::AmdDefineWithDependenciesCodeGen(v) => v.code_generation(ctx).await,
206            Self::CjsRequireCacheAccess(v) => v.code_generation(ctx).await,
207            Self::ConstantConditionCodeGen(v) => v.code_generation(ctx).await,
208            Self::ConstantValueCodeGen(v) => v.code_generation(ctx).await,
209            Self::DynamicExpression(v) => v.code_generation(ctx).await,
210            Self::EsmBinding(v) => v.code_generation(ctx, scope_hoisting_context).await,
211            Self::EsmModuleItem(v) => v.code_generation(ctx).await,
212            Self::IdentReplacement(v) => v.code_generation(ctx).await,
213            Self::ImportMetaBinding(v) => v.code_generation(ctx).await,
214            Self::ImportMetaRef(v) => v.code_generation(ctx).await,
215            Self::MemberReplacement(v) => v.code_generation(ctx).await,
216            Self::Unreachable(v) => v.code_generation(ctx).await,
217            Self::CjsRequireAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
218            Self::CjsRequireResolveAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
219            Self::EsmAsyncAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
220            Self::EsmModuleIdAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
221            Self::RequireContextAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
222            Self::UrlAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
223            Self::WorkerAssetReferenceCodeGen(v) => v.code_generation(ctx).await,
224        }
225    }
226}
227
228#[turbo_tasks::value(transparent)]
229pub struct CodeGens(Vec<CodeGen>);
230
231#[turbo_tasks::value_impl]
232impl CodeGens {
233    #[turbo_tasks::function]
234    pub fn empty() -> Vc<Self> {
235        Vc::cell(Vec::new())
236    }
237}
238
239pub trait IntoCodeGenReference {
240    fn into_code_gen_reference(
241        self,
242        path: AstPath,
243    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen);
244}
245
246pub fn path_to(
247    path: &[AstParentKind],
248    f: impl FnMut(&AstParentKind) -> bool,
249) -> Vec<AstParentKind> {
250    if let Some(pos) = path.iter().rev().position(f) {
251        let index = path.len() - pos - 1;
252        path[..index].to_vec()
253    } else {
254        path.to_vec()
255    }
256}
257
258/// Creates a single-method visitor that will visit the AST nodes matching the
259/// provided path.
260///
261/// If you pass in `exact`, the visitor will only visit the nodes that match the
262/// path exactly. Otherwise, the visitor will visit the closest matching parent
263/// node in the path.
264///
265/// Refer to the [swc_core::ecma::visit::VisitMut] trait for a list of all
266/// possible visit methods.
267#[macro_export]
268macro_rules! create_visitor {
269    (exact, $ast_path:expr, $name:ident, |$arg:ident: &mut $ty:ident| $b:block) => {
270        $crate::create_visitor!(__ $ast_path.to_vec(), $name, |$arg: &mut $ty| $b)
271    };
272    ($ast_path:expr, $name:ident, |$arg:ident: &mut $ty:ident| $b:block) => {
273        $crate::create_visitor!(__ $crate::code_gen::path_to(&$ast_path, |n| {
274            matches!(n, swc_core::ecma::visit::AstParentKind::$ty(_))
275        }), $name, |$arg: &mut $ty| $b)
276    };
277    (__ $ast_path:expr, $name:ident, |$arg:ident: &mut $ty:ident| $b:block) => {{
278        struct Visitor<T: Fn(&mut swc_core::ecma::ast::$ty) + Send + Sync> {
279            $name: T,
280        }
281
282        impl<T: Fn(&mut swc_core::ecma::ast::$ty) + Send + Sync> $crate::code_gen::AstModifier
283            for Visitor<T>
284        {
285            fn $name(&self, $arg: &mut swc_core::ecma::ast::$ty) {
286                (self.$name)($arg);
287            }
288        }
289
290        {
291            #[cfg(debug_assertions)]
292            if $ast_path.is_empty() {
293                unreachable!("if the path is empty, the visitor should be a root visitor");
294            }
295
296            (
297                $ast_path,
298                Box::new(Visitor {
299                    $name: move |$arg: &mut swc_core::ecma::ast::$ty| $b,
300                }) as Box<dyn $crate::code_gen::AstModifier>,
301            )
302        }
303    }};
304}