turbopack_ecmascript/
code_gen.rs

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