Skip to main content

turbopack_ecmascript/
code_gen.rs

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