Skip to content

Commit 21e2df5

Browse files
committed
refactor(minifier): replace VisitMut with Traverse for inject and define plugins (#5705)
closes #5704
1 parent 945d274 commit 21e2df5

File tree

4 files changed

+68
-56
lines changed

4 files changed

+68
-56
lines changed

crates/oxc_minifier/src/plugins/inject_global_variables.rs

+32-25
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
use std::sync::Arc;
2+
13
use cow_utils::CowUtils;
4+
25
use oxc_allocator::Allocator;
3-
use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut};
6+
use oxc_ast::{ast::*, AstBuilder};
47
use oxc_semantic::{ScopeTree, SymbolTable};
58
use oxc_span::{CompactStr, SPAN};
6-
use std::sync::Arc;
9+
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
710

811
use super::replace_global_defines::{DotDefine, ReplaceGlobalDefines};
912

@@ -100,12 +103,18 @@ impl<'a> From<&InjectImport> for DotDefineState<'a> {
100103
}
101104
}
102105

106+
#[must_use]
107+
pub struct InjectGlobalVariablesReturn {
108+
pub symbols: SymbolTable,
109+
pub scopes: ScopeTree,
110+
}
111+
103112
/// Injects import statements for global variables.
104113
///
105114
/// References:
106115
///
107116
/// * <https://www.npmjs.com/package/@rollup/plugin-inject>
108-
pub struct InjectGlobalVariables<'a, 'b> {
117+
pub struct InjectGlobalVariables<'a> {
109118
ast: AstBuilder<'a>,
110119
config: InjectGlobalVariablesConfig,
111120

@@ -116,36 +125,32 @@ pub struct InjectGlobalVariables<'a, 'b> {
116125
/// Identifiers for which dot define replaced a member expression.
117126
replaced_dot_defines:
118127
Vec<(/* identifier of member expression */ CompactStr, /* local */ CompactStr)>,
119-
120-
symbols: &'b mut SymbolTable, // will be used to keep symbols in sync
121-
scopes: &'b mut ScopeTree,
122128
}
123129

124-
impl<'a, 'b> VisitMut<'a> for InjectGlobalVariables<'a, 'b> {
125-
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
126-
self.replace_dot_defines(expr);
127-
walk_mut::walk_expression(self, expr);
130+
impl<'a> Traverse<'a> for InjectGlobalVariables<'a> {
131+
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
132+
self.replace_dot_defines(expr, ctx);
128133
}
129134
}
130135

131-
impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
132-
pub fn new(
133-
allocator: &'a Allocator,
134-
symbols: &'b mut SymbolTable,
135-
scopes: &'b mut ScopeTree,
136-
config: InjectGlobalVariablesConfig,
137-
) -> Self {
136+
impl<'a> InjectGlobalVariables<'a> {
137+
pub fn new(allocator: &'a Allocator, config: InjectGlobalVariablesConfig) -> Self {
138138
Self {
139139
ast: AstBuilder::new(allocator),
140140
config,
141141
dot_defines: vec![],
142142
replaced_dot_defines: vec![],
143-
symbols,
144-
scopes,
145143
}
146144
}
147145

148-
pub fn build(&mut self, program: &mut Program<'a>) {
146+
pub fn build(
147+
&mut self,
148+
symbols: SymbolTable,
149+
scopes: ScopeTree,
150+
program: &mut Program<'a>,
151+
) -> InjectGlobalVariablesReturn {
152+
let mut symbols = symbols;
153+
let mut scopes = scopes;
149154
// Step 1: slow path where visiting the AST is required to replace dot defines.
150155
let dot_defines = self
151156
.config
@@ -157,7 +162,7 @@ impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
157162

158163
if !dot_defines.is_empty() {
159164
self.dot_defines = dot_defines;
160-
self.visit_program(program);
165+
(symbols, scopes) = traverse_mut(self, self.ast.allocator, program, symbols, scopes);
161166
}
162167

163168
// Step 2: find all the injects that are referenced.
@@ -172,17 +177,19 @@ impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
172177
} else if self.replaced_dot_defines.iter().any(|d| d.0 == i.specifier.local()) {
173178
false
174179
} else {
175-
self.scopes.root_unresolved_references().contains_key(i.specifier.local())
180+
scopes.root_unresolved_references().contains_key(i.specifier.local())
176181
}
177182
})
178183
.cloned()
179184
.collect::<Vec<_>>();
180185

181186
if injects.is_empty() {
182-
return;
187+
return InjectGlobalVariablesReturn { symbols, scopes };
183188
}
184189

185190
self.inject_imports(&injects, program);
191+
192+
InjectGlobalVariablesReturn { symbols, scopes }
186193
}
187194

188195
fn inject_imports(&self, injects: &[InjectImport], program: &mut Program<'a>) {
@@ -227,10 +234,10 @@ impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
227234
}
228235
}
229236

230-
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>) {
237+
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
231238
if let Expression::StaticMemberExpression(member) = expr {
232239
for DotDefineState { dot_define, value_atom } in &mut self.dot_defines {
233-
if ReplaceGlobalDefines::is_dot_define(self.symbols, dot_define, member) {
240+
if ReplaceGlobalDefines::is_dot_define(ctx.symbols(), dot_define, member) {
234241
// If this is first replacement made for this dot define,
235242
// create `Atom` for replacement, and record in `replaced_dot_defines`
236243
if value_atom.is_none() {

crates/oxc_minifier/src/plugins/replace_global_defines.rs

+32-27
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::{cmp::Ordering, sync::Arc};
22

33
use oxc_allocator::Allocator;
4-
use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut};
4+
use oxc_ast::ast::*;
55
use oxc_diagnostics::OxcDiagnostic;
66
use oxc_parser::Parser;
7-
use oxc_semantic::{IsGlobalReference, SymbolTable};
7+
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
88
use oxc_span::{CompactStr, SourceType};
99
use oxc_syntax::identifier::is_identifier_name;
10+
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
1011

1112
/// Configuration for [ReplaceGlobalDefines].
1213
///
@@ -162,53 +163,57 @@ impl ReplaceGlobalDefinesConfig {
162163
}
163164
}
164165

166+
#[must_use]
167+
pub struct ReplaceGlobalDefinesReturn {
168+
pub symbols: SymbolTable,
169+
pub scopes: ScopeTree,
170+
}
171+
165172
/// Replace Global Defines.
166173
///
167174
/// References:
168175
///
169176
/// * <https://esbuild.github.io/api/#define>
170177
/// * <https://github.com/terser/terser?tab=readme-ov-file#conditional-compilation>
171178
/// * <https://github.com/evanw/esbuild/blob/9c13ae1f06dfa909eb4a53882e3b7e4216a503fe/internal/config/globals.go#L852-L1014>
172-
pub struct ReplaceGlobalDefines<'a, 'b> {
173-
ast: AstBuilder<'a>,
174-
symbols: &'b mut SymbolTable,
179+
pub struct ReplaceGlobalDefines<'a> {
180+
allocator: &'a Allocator,
175181
config: ReplaceGlobalDefinesConfig,
176182
}
177183

178-
impl<'a, 'b> VisitMut<'a> for ReplaceGlobalDefines<'a, 'b> {
179-
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
180-
self.replace_identifier_defines(expr);
181-
self.replace_dot_defines(expr);
182-
walk_mut::walk_expression(self, expr);
184+
impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> {
185+
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
186+
self.replace_identifier_defines(expr, ctx);
187+
self.replace_dot_defines(expr, ctx);
183188
}
184189
}
185190

186-
impl<'a, 'b> ReplaceGlobalDefines<'a, 'b> {
187-
pub fn new(
188-
allocator: &'a Allocator,
189-
symbols: &'b mut SymbolTable,
190-
config: ReplaceGlobalDefinesConfig,
191-
) -> Self {
192-
Self { ast: AstBuilder::new(allocator), symbols, config }
191+
impl<'a> ReplaceGlobalDefines<'a> {
192+
pub fn new(allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig) -> Self {
193+
Self { allocator, config }
193194
}
194195

195-
pub fn build(&mut self, program: &mut Program<'a>) {
196-
self.visit_program(program);
196+
pub fn build(
197+
&mut self,
198+
symbols: SymbolTable,
199+
scopes: ScopeTree,
200+
program: &mut Program<'a>,
201+
) -> ReplaceGlobalDefinesReturn {
202+
let (symbols, scopes) = traverse_mut(self, self.allocator, program, symbols, scopes);
203+
ReplaceGlobalDefinesReturn { symbols, scopes }
197204
}
198205

199206
// Construct a new expression because we don't have ast clone right now.
200207
fn parse_value(&self, source_text: &str) -> Expression<'a> {
201208
// Allocate the string lazily because replacement happens rarely.
202-
let source_text = self.ast.allocator.alloc(source_text.to_string());
209+
let source_text = self.allocator.alloc(source_text.to_string());
203210
// Unwrapping here, it should already be checked by [ReplaceGlobalDefinesConfig::new].
204-
Parser::new(self.ast.allocator, source_text, SourceType::default())
205-
.parse_expression()
206-
.unwrap()
211+
Parser::new(self.allocator, source_text, SourceType::default()).parse_expression().unwrap()
207212
}
208213

209-
fn replace_identifier_defines(&self, expr: &mut Expression<'a>) {
214+
fn replace_identifier_defines(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
210215
let Expression::Identifier(ident) = expr else { return };
211-
if !ident.is_global_reference(self.symbols) {
216+
if !ident.is_global_reference(ctx.symbols()) {
212217
return;
213218
}
214219
for (key, value) in &self.config.0.identifier {
@@ -220,12 +225,12 @@ impl<'a, 'b> ReplaceGlobalDefines<'a, 'b> {
220225
}
221226
}
222227

223-
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>) {
228+
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
224229
let Expression::StaticMemberExpression(member) = expr else {
225230
return;
226231
};
227232
for dot_define in &self.config.0.dot {
228-
if Self::is_dot_define(self.symbols, dot_define, member) {
233+
if Self::is_dot_define(ctx.symbols(), dot_define, member) {
229234
let value = self.parse_value(&dot_define.value);
230235
*expr = value;
231236
return;

crates/oxc_minifier/tests/plugins/inject_global_variables.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ pub(crate) fn test(source_text: &str, expected: &str, config: InjectGlobalVariab
1616
let allocator = Allocator::default();
1717
let ret = Parser::new(&allocator, source_text, source_type).parse();
1818
let program = allocator.alloc(ret.program);
19-
let (mut symbols, mut scopes) = SemanticBuilder::new(source_text)
19+
let (symbols, scopes) = SemanticBuilder::new(source_text)
2020
.build(program)
2121
.semantic
2222
.into_symbol_table_and_scope_tree();
23-
InjectGlobalVariables::new(&allocator, &mut symbols, &mut scopes, config).build(program);
23+
let _ = InjectGlobalVariables::new(&allocator, config).build(symbols, scopes, program);
2424
let result = CodeGenerator::new()
2525
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
2626
.build(program)

crates/oxc_minifier/tests/plugins/replace_global_defines.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ pub(crate) fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefin
1212
let allocator = Allocator::default();
1313
let ret = Parser::new(&allocator, source_text, source_type).parse();
1414
let program = allocator.alloc(ret.program);
15-
let (mut symbols, _scopes) = SemanticBuilder::new(source_text)
15+
let (symbols, scopes) = SemanticBuilder::new(source_text)
1616
.build(program)
1717
.semantic
1818
.into_symbol_table_and_scope_tree();
19-
ReplaceGlobalDefines::new(&allocator, &mut symbols, config).build(program);
19+
let _ = ReplaceGlobalDefines::new(&allocator, config).build(symbols, scopes, program);
2020
let result = CodeGenerator::new()
2121
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
2222
.build(program)

0 commit comments

Comments
 (0)