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