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