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, FunctionBody, Lit, MemberExpr, ModuleDecl, ModuleItem, Pat,
8            Program, Prop, SimpleAssignTarget, Stmt, Str, SwitchCase,
9        },
10        visit::AstParentKind,
11    },
12};
13use turbo_rcstr::RcStr;
14use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, debug::ValueDebugFormat};
15use turbopack_core::{chunk::ChunkingContext, reference::ModuleReference};
16
17use crate::{
18    ScopeHoistingContext,
19    ast_path_trie::{AstPathId, AstPathTrie, AstPathTrieBuilder},
20    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
21    references::{
22        amd::AmdDefineWithDependenciesCodeGen,
23        cjs::{
24            CjsExportsDropCodeGen, CjsRequireAssetReferenceCodeGen,
25            CjsRequireResolveAssetReferenceCodeGen,
26        },
27        constant_condition::ConstantConditionCodeGen,
28        constant_value::ConstantValueCodeGen,
29        dynamic_expression::DynamicExpression,
30        emit_collect::CollectReferenceCodeGen,
31        esm::{
32            EsmBindings, EsmModuleItem, ImportMetaBinding, ImportMetaRef,
33            dynamic::EsmAsyncAssetReferenceCodeGen, module_id::EsmModuleIdAssetReferenceCodeGen,
34            url::UrlAssetReferenceCodeGen,
35        },
36        exports_info::{ExportsInfoBinding, ExportsInfoRef},
37        hot_module::ModuleHotReferenceCodeGen,
38        ident::IdentReplacement,
39        import_meta_glob::ImportMetaGlobAssetReferenceCodeGen,
40        member::MemberReplacement,
41        removal::RemovalCodeGen,
42        require_context::RequireContextAssetReferenceCodeGen,
43        service_worker::ServiceWorkerAssetReferenceCodeGen,
44        worker::{WorkerAssetReferenceCodeGen, WorkerGlobalsReplacementCodeGen},
45    },
46};
47
48#[derive(Default)]
49pub struct CodeGeneration {
50    /// ast nodes matching the span will be visitor by the visitor
51    pub visitors: Vec<(AstPathId, Box<dyn AstModifier>)>,
52    pub hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
53    pub early_hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
54    pub late_stmts: Vec<CodeGenerationHoistedStmt>,
55    pub early_late_stmts: Vec<CodeGenerationHoistedStmt>,
56    pub comments: Option<SwcComments>,
57}
58
59impl CodeGeneration {
60    pub fn empty() -> Self {
61        CodeGeneration {
62            ..Default::default()
63        }
64    }
65
66    pub fn new(
67        visitors: Vec<(AstPathId, Box<dyn AstModifier>)>,
68        hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
69        early_hoisted_stmts: Vec<CodeGenerationHoistedStmt>,
70        late_stmts: Vec<CodeGenerationHoistedStmt>,
71        early_late_stmts: Vec<CodeGenerationHoistedStmt>,
72    ) -> Self {
73        CodeGeneration {
74            visitors,
75            hoisted_stmts,
76            early_hoisted_stmts,
77            late_stmts,
78            early_late_stmts,
79            ..Default::default()
80        }
81    }
82
83    pub fn visitors_with_comments(
84        visitors: Vec<(AstPathId, Box<dyn AstModifier>)>,
85        comments: SwcComments,
86    ) -> Self {
87        CodeGeneration {
88            visitors,
89            comments: Some(comments),
90            ..Default::default()
91        }
92    }
93
94    pub fn visitors(visitors: Vec<(AstPathId, Box<dyn AstModifier>)>) -> Self {
95        CodeGeneration {
96            visitors,
97            ..Default::default()
98        }
99    }
100
101    pub fn hoisted_stmt(key: RcStr, stmt: Stmt) -> Self {
102        CodeGeneration {
103            hoisted_stmts: vec![CodeGenerationHoistedStmt::new(key, stmt)],
104            ..Default::default()
105        }
106    }
107
108    pub fn hoisted_stmts(hoisted_stmts: Vec<CodeGenerationHoistedStmt>) -> Self {
109        CodeGeneration {
110            hoisted_stmts,
111            ..Default::default()
112        }
113    }
114}
115
116#[derive(Clone)]
117pub struct CodeGenerationHoistedStmt {
118    pub key: RcStr,
119    pub stmt: Stmt,
120}
121
122impl CodeGenerationHoistedStmt {
123    pub fn new(key: RcStr, stmt: Stmt) -> Self {
124        CodeGenerationHoistedStmt { key, stmt }
125    }
126}
127
128macro_rules! method {
129    ($name:ident, $T:ty) => {
130        fn $name(&self, _node: &mut $T) {}
131    };
132}
133
134pub trait AstModifier: Send + Sync {
135    method!(visit_mut_prop, Prop);
136    method!(visit_mut_simple_assign_target, SimpleAssignTarget);
137    method!(visit_mut_expr, Expr);
138    method!(visit_mut_member_expr, MemberExpr);
139    method!(visit_mut_pat, Pat);
140    method!(visit_mut_stmt, Stmt);
141    method!(visit_mut_module_decl, ModuleDecl);
142    method!(visit_mut_module_item, ModuleItem);
143    method!(visit_mut_call_expr, CallExpr);
144    method!(visit_mut_lit, Lit);
145    method!(visit_mut_str, Str);
146    method!(visit_mut_block_stmt, BlockStmt);
147    method!(visit_mut_function_body, FunctionBody);
148    method!(visit_mut_switch_case, SwitchCase);
149    method!(visit_mut_program, Program);
150}
151
152pub trait ModifiableAst {
153    fn modify(&mut self, modifier: &dyn AstModifier);
154}
155
156macro_rules! impl_modify {
157    ($visit_mut_name:ident, $T:ty) => {
158        impl ModifiableAst for $T {
159            fn modify(&mut self, modifier: &dyn AstModifier) {
160                modifier.$visit_mut_name(self)
161            }
162        }
163    };
164}
165
166impl_modify!(visit_mut_prop, Prop);
167impl_modify!(visit_mut_simple_assign_target, SimpleAssignTarget);
168impl_modify!(visit_mut_expr, Expr);
169impl_modify!(visit_mut_member_expr, MemberExpr);
170impl_modify!(visit_mut_pat, Pat);
171impl_modify!(visit_mut_stmt, Stmt);
172impl_modify!(visit_mut_module_decl, ModuleDecl);
173impl_modify!(visit_mut_module_item, ModuleItem);
174impl_modify!(visit_mut_call_expr, CallExpr);
175impl_modify!(visit_mut_lit, Lit);
176impl_modify!(visit_mut_str, Str);
177impl_modify!(visit_mut_block_stmt, BlockStmt);
178impl_modify!(visit_mut_function_body, FunctionBody);
179impl_modify!(visit_mut_switch_case, SwitchCase);
180impl_modify!(visit_mut_program, Program);
181
182#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
183pub enum CodeGen {
184    // AMD occurs very rarely and makes the enum much bigger
185    AmdDefineWithDependenciesCodeGen(Box<AmdDefineWithDependenciesCodeGen>),
186    CollectReferenceCodeGen(CollectReferenceCodeGen),
187    ConstantConditionCodeGen(ConstantConditionCodeGen),
188    ConstantValueCodeGen(ConstantValueCodeGen),
189    DynamicExpression(DynamicExpression),
190    EsmBindings(EsmBindings),
191    EsmModuleItem(EsmModuleItem),
192    ExportsInfoBinding(ExportsInfoBinding),
193    ExportsInfoRef(ExportsInfoRef),
194    CjsExportsDropCodeGen(CjsExportsDropCodeGen),
195    IdentReplacement(IdentReplacement),
196    ImportMetaBinding(ImportMetaBinding),
197    ImportMetaRef(ImportMetaRef),
198    MemberReplacement(MemberReplacement),
199    RemovalCodeGen(RemovalCodeGen),
200    CjsRequireAssetReferenceCodeGen(CjsRequireAssetReferenceCodeGen),
201    CjsRequireResolveAssetReferenceCodeGen(CjsRequireResolveAssetReferenceCodeGen),
202    EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen),
203    EsmModuleIdAssetReferenceCodeGen(EsmModuleIdAssetReferenceCodeGen),
204    ImportMetaGlobAssetReferenceCodeGen(ImportMetaGlobAssetReferenceCodeGen),
205    RequireContextAssetReferenceCodeGen(RequireContextAssetReferenceCodeGen),
206    UrlAssetReferenceCodeGen(UrlAssetReferenceCodeGen),
207    WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen),
208    ServiceWorkerAssetReferenceCodeGen(ServiceWorkerAssetReferenceCodeGen),
209    ModuleHotReferenceCodeGen(ModuleHotReferenceCodeGen),
210    WorkerGlobalsReplacementCodeGen(WorkerGlobalsReplacementCodeGen),
211}
212
213impl CodeGen {
214    pub async fn code_generation(
215        &self,
216        trie: &AstPathTrie,
217        ctx: Vc<Box<dyn ChunkingContext>>,
218        scope_hoisting_context: ScopeHoistingContext<'_>,
219        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
220        exports: ResolvedVc<EcmascriptExports>,
221    ) -> Result<CodeGeneration> {
222        match self {
223            Self::AmdDefineWithDependenciesCodeGen(v) => v.code_generation(trie, ctx).await,
224            Self::ConstantConditionCodeGen(v) => v.code_generation(trie, ctx).await,
225            Self::CollectReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
226            Self::ConstantValueCodeGen(v) => v.code_generation(trie, ctx).await,
227            Self::DynamicExpression(v) => v.code_generation(trie, ctx).await,
228            Self::EsmBindings(v) => v.code_generation(trie, ctx, scope_hoisting_context).await,
229            Self::EsmModuleItem(v) => v.code_generation(trie, ctx).await,
230            Self::ExportsInfoBinding(v) => v.code_generation(trie, ctx, module, exports).await,
231            Self::ExportsInfoRef(v) => v.code_generation(trie, ctx).await,
232            Self::CjsExportsDropCodeGen(v) => v.code_generation(trie, ctx, module, exports).await,
233            Self::IdentReplacement(v) => v.code_generation(trie, ctx).await,
234            Self::ImportMetaBinding(v) => v.code_generation(trie, ctx).await,
235            Self::ImportMetaRef(v) => v.code_generation(trie, ctx).await,
236            Self::MemberReplacement(v) => v.code_generation(trie, ctx).await,
237            Self::RemovalCodeGen(v) => v.code_generation(trie, ctx).await,
238            Self::CjsRequireAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
239            Self::CjsRequireResolveAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
240            Self::EsmAsyncAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
241            Self::EsmModuleIdAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
242            Self::ImportMetaGlobAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
243            Self::RequireContextAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
244            Self::UrlAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
245            Self::WorkerAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
246            Self::ServiceWorkerAssetReferenceCodeGen(v) => v.code_generation(trie, ctx).await,
247            Self::ModuleHotReferenceCodeGen(v) => {
248                v.code_generation(trie, ctx, scope_hoisting_context).await
249            }
250            Self::WorkerGlobalsReplacementCodeGen(v) => v.code_generation(trie, ctx).await,
251        }
252    }
253}
254
255/// The code generations for a module, together with the trie their [`AstPathId`]s index into.
256///
257/// The trie is shared by every code generation here, so it is stored once alongside them
258/// rather than being cloned into each one.
259#[turbo_tasks::value(shared)]
260pub struct CodeGens {
261    pub code_gens: Vec<CodeGen>,
262    pub ast_paths: AstPathTrie,
263}
264
265#[turbo_tasks::value_impl]
266impl CodeGens {
267    #[turbo_tasks::function]
268    pub fn empty() -> Vc<Self> {
269        CodeGens {
270            code_gens: Vec::new(),
271            ast_paths: AstPathTrie::default(),
272        }
273        .cell()
274    }
275}
276
277pub trait IntoCodeGenReference {
278    fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>>;
279    fn into_code_gen_reference(
280        self,
281        trie: &AstPathTrieBuilder,
282        path: AstPathId,
283    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen);
284}
285
286/// The prefix of `path` up to (excluding) its innermost element matching `f`.
287///
288/// Returns the whole path when nothing matches.
289pub fn path_to(
290    trie: &AstPathTrie,
291    path: AstPathId,
292    f: impl FnMut(&AstParentKind) -> bool,
293) -> AstPathId {
294    // Everything from the innermost match down is dropped; with no match the path is used
295    // as-is.
296    trie.find_last(path, f)
297        .map_or(path, |found| trie.parent_or_root(found))
298}
299
300/// Creates a single-method visitor that will visit the AST nodes matching the
301/// provided path.
302///
303/// If you pass in `exact`, the visitor will only visit the nodes that match the
304/// path exactly. Otherwise, the visitor will visit the closest matching parent
305/// node in the path.
306///
307/// Refer to the [swc_core::ecma::visit::VisitMut] trait for a list of all
308/// possible visit methods.
309#[macro_export]
310macro_rules! create_visitor {
311    // `exact` means the path already points at the node to modify, so unlike the arm below
312    // it needs no trie to find an enclosing node.
313    (exact, $trie:expr, $ast_path:expr, $name:ident, |$arg:ident: &mut $ty:ident| $b:block) => {
314        $crate::create_visitor!(__ $ast_path, $name, |$arg: &mut $ty| $b)
315    };
316    ($trie:expr, $ast_path:expr, $name:ident, |$arg:ident: &mut $ty:ident| $b:block) => {
317        $crate::create_visitor!(__ $crate::code_gen::path_to($trie, $ast_path, |n| {
318            matches!(n, swc_core::ecma::visit::AstParentKind::$ty(_))
319        }), $name, |$arg: &mut $ty| $b)
320    };
321    (__ $ast_path:expr, $name:ident, |$arg:ident: &mut $ty:ident| $b:block) => {{
322        struct Visitor<T: Fn(&mut swc_core::ecma::ast::$ty) + Send + Sync> {
323            $name: T,
324        }
325
326        impl<T: Fn(&mut swc_core::ecma::ast::$ty) + Send + Sync> $crate::code_gen::AstModifier
327            for Visitor<T>
328        {
329            fn $name(&self, $arg: &mut swc_core::ecma::ast::$ty) {
330                (self.$name)($arg);
331            }
332        }
333
334        {
335            #[cfg(debug_assertions)]
336            if $ast_path.is_root() {
337                unreachable!("if the path is empty, the visitor should be a root visitor");
338            }
339
340            (
341                $ast_path,
342                Box::new(Visitor {
343                    $name: move |$arg: &mut swc_core::ecma::ast::$ty| $b,
344                }) as Box<dyn $crate::code_gen::AstModifier>,
345            )
346        }
347    }};
348}