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