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