diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index 72b0dce487c5a..aaeeb334c9fb8 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -185,16 +185,10 @@ pub trait CompilerInterface { } if let Some(options) = define_options { - let _ret = ReplaceGlobalDefines::new(&allocator, options).build(scoping, &mut program); + let ret = ReplaceGlobalDefines::new(&allocator, options).build(scoping, &mut program); + scoping = ret.scoping; // Run DCE if minification is disabled. if self.compress_options().is_none() { - // Rebuild semantic because define plugin changed the AST. - // DCE assumes semantic data to be correct, it will crash otherwise. - scoping = SemanticBuilder::new() - .with_stats(stats) - .build(&program) - .semantic - .into_scoping(); Compressor::new(&allocator).dead_code_elimination_with_scoping( &mut program, scoping, diff --git a/crates/oxc_transformer_plugins/src/replace_global_defines.rs b/crates/oxc_transformer_plugins/src/replace_global_defines.rs index e8ad56546a993..24e89884f51ce 100644 --- a/crates/oxc_transformer_plugins/src/replace_global_defines.rs +++ b/crates/oxc_transformer_plugins/src/replace_global_defines.rs @@ -4,10 +4,10 @@ use rustc_hash::FxHashSet; use oxc_allocator::{Address, Allocator, GetAddress}; use oxc_ast::ast::*; -use oxc_ast_visit::VisitMut; +use oxc_ast_visit::{VisitMut, walk_mut}; use oxc_diagnostics::OxcDiagnostic; use oxc_parser::Parser; -use oxc_semantic::{IsGlobalReference, ScopeFlags, Scoping}; +use oxc_semantic::{IsGlobalReference, ReferenceFlags, ScopeFlags, Scoping}; use oxc_span::{CompactStr, SPAN, SourceType}; use oxc_syntax::identifier::is_identifier_name; use oxc_traverse::{Ancestor, Traverse, traverse_mut}; @@ -288,7 +288,7 @@ impl<'a> ReplaceGlobalDefines<'a> { } // Construct a new expression because we don't have ast clone right now. - fn parse_value(&self, source_text: &str) -> Expression<'a> { + fn parse_value(&self, source_text: &str, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { // Allocate the string lazily because replacement happens rarely. let source_text = self.allocator.alloc_str(source_text); // Unwrapping here, it should already be checked by [ReplaceGlobalDefinesConfig::new]. @@ -296,12 +296,16 @@ impl<'a> ReplaceGlobalDefines<'a> { .parse_expression() .unwrap(); - RemoveSpans.visit_expression(&mut expr); + UpdateReplacedExpression { ctx }.visit_expression(&mut expr); expr } - fn replace_identifier_defines(&self, expr: &mut Expression<'a>, ctx: &TraverseCtx<'a>) -> bool { + fn replace_identifier_defines( + &self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> bool { match expr { Expression::Identifier(ident) => { if let Some(new_expr) = self.replace_identifier_define_impl(ident, ctx) { @@ -315,7 +319,7 @@ impl<'a> ReplaceGlobalDefines<'a> { { for (key, value) in &self.config.0.identifier.identifier_defines { if key.as_str() == "this" { - let value = self.parse_value(value); + let value = self.parse_value(value, ctx); *expr = value; return true; @@ -330,7 +334,7 @@ impl<'a> ReplaceGlobalDefines<'a> { fn replace_identifier_define_impl( &self, ident: &oxc_allocator::Box<'_, IdentifierReference<'_>>, - ctx: &TraverseCtx<'a>, + ctx: &mut TraverseCtx<'a>, ) -> Option> { if let Some(symbol_id) = ident .reference_id @@ -345,7 +349,7 @@ impl<'a> ReplaceGlobalDefines<'a> { // This is a global variable, including ambient variants such as `declare const`. for (key, value) in &self.config.0.identifier.identifier_defines { if ident.name.as_str() == key { - let value = self.parse_value(value); + let value = self.parse_value(value, ctx); return Some(value); } } @@ -355,17 +359,17 @@ impl<'a> ReplaceGlobalDefines<'a> { fn replace_define_with_assignment_expr( &self, node: &mut AssignmentExpression<'a>, - ctx: &TraverseCtx<'a>, + ctx: &mut TraverseCtx<'a>, ) -> bool { let new_left = node .left .as_simple_assignment_target_mut() .and_then(|item| match item { SimpleAssignmentTarget::ComputedMemberExpression(computed_member_expr) => { - self.replace_dot_computed_member_expr(ctx, computed_member_expr) + self.replace_dot_computed_member_expr(computed_member_expr, ctx) } SimpleAssignmentTarget::StaticMemberExpression(member) => { - self.replace_dot_static_member_expr(ctx, member) + self.replace_dot_static_member_expr(member, ctx) } SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => { self.replace_identifier_define_impl(ident, ctx) @@ -380,16 +384,16 @@ impl<'a> ReplaceGlobalDefines<'a> { false } - fn replace_dot_defines(&self, expr: &mut Expression<'a>, ctx: &TraverseCtx<'a>) -> bool { + fn replace_dot_defines(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool { match expr { Expression::ChainExpression(chain) => { let Some(new_expr) = chain.expression.as_member_expression_mut().and_then(|item| match item { MemberExpression::ComputedMemberExpression(computed_member_expr) => { - self.replace_dot_computed_member_expr(ctx, computed_member_expr) + self.replace_dot_computed_member_expr(computed_member_expr, ctx) } MemberExpression::StaticMemberExpression(member) => { - self.replace_dot_static_member_expr(ctx, member) + self.replace_dot_static_member_expr(member, ctx) } MemberExpression::PrivateFieldExpression(_) => None, }) @@ -400,13 +404,13 @@ impl<'a> ReplaceGlobalDefines<'a> { return true; } Expression::StaticMemberExpression(member) => { - if let Some(new_expr) = self.replace_dot_static_member_expr(ctx, member) { + if let Some(new_expr) = self.replace_dot_static_member_expr(member, ctx) { *expr = new_expr; return true; } } Expression::ComputedMemberExpression(member) => { - if let Some(new_expr) = self.replace_dot_computed_member_expr(ctx, member) { + if let Some(new_expr) = self.replace_dot_computed_member_expr(member, ctx) { *expr = new_expr; return true; } @@ -416,7 +420,7 @@ impl<'a> ReplaceGlobalDefines<'a> { && meta_property.meta.name == "import" && meta_property.property.name == "meta" { - let value = self.parse_value(replacement); + let value = self.parse_value(replacement, ctx); *expr = value; return true; } @@ -428,8 +432,8 @@ impl<'a> ReplaceGlobalDefines<'a> { fn replace_dot_computed_member_expr( &self, - ctx: &TraverseCtx<'a>, member: &ComputedMemberExpression<'a>, + ctx: &mut TraverseCtx<'a>, ) -> Option> { for dot_define in &self.config.0.dot { if Self::is_dot_define( @@ -437,7 +441,7 @@ impl<'a> ReplaceGlobalDefines<'a> { dot_define, DotDefineMemberExpression::ComputedMemberExpression(member), ) { - let value = self.parse_value(&dot_define.value); + let value = self.parse_value(&dot_define.value, ctx); return Some(value); } } @@ -447,8 +451,8 @@ impl<'a> ReplaceGlobalDefines<'a> { fn replace_dot_static_member_expr( &self, - ctx: &TraverseCtx<'a>, member: &StaticMemberExpression<'a>, + ctx: &mut TraverseCtx<'a>, ) -> Option> { for dot_define in &self.config.0.dot { if Self::is_dot_define( @@ -456,13 +460,13 @@ impl<'a> ReplaceGlobalDefines<'a> { dot_define, DotDefineMemberExpression::StaticMemberExpression(member), ) { - let value = self.parse_value(&dot_define.value); + let value = self.parse_value(&dot_define.value, ctx); return Some(destructing_dot_define_optimizer(value, ctx)); } } for meta_property_define in &self.config.0.meta_property { if Self::is_meta_property_define(meta_property_define, member) { - let value = self.parse_value(&meta_property_define.value); + let value = self.parse_value(&meta_property_define.value, ctx); return Some(destructing_dot_define_optimizer(value, ctx)); } } @@ -722,9 +726,21 @@ fn assignment_target_from_expr(expr: Expression) -> Option { } } -struct RemoveSpans; +/// Update the replaced expression: +/// * change spans to empty spans for sourcemap +/// * assign reference id in current scope +struct UpdateReplacedExpression<'a, 'b> { + ctx: &'b mut TraverseCtx<'a>, +} + +impl VisitMut<'_> for UpdateReplacedExpression<'_, '_> { + fn visit_identifier_reference(&mut self, ident: &mut IdentifierReference<'_>) { + let reference_id = + self.ctx.create_reference_in_current_scope(ident.name.as_str(), ReferenceFlags::Read); + ident.set_reference_id(reference_id); + walk_mut::walk_identifier_reference(self, ident); + } -impl VisitMut<'_> for RemoveSpans { fn visit_span(&mut self, span: &mut Span) { *span = SPAN; } diff --git a/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs b/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs index 14ae55d2c1d89..d84009d2daf9b 100644 --- a/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs +++ b/crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs @@ -1,4 +1,5 @@ use oxc_allocator::Allocator; +use oxc_ast_visit::Visit; use oxc_codegen::{Codegen, CodegenOptions}; use oxc_minifier::{CompressOptions, Compressor}; use oxc_parser::Parser; @@ -15,10 +16,12 @@ pub fn test(source_text: &str, expected: &str, config: &ReplaceGlobalDefinesConf let ret = Parser::new(&allocator, source_text, source_type).parse(); assert!(ret.errors.is_empty()); let mut program = ret.program; - let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping(); - let _ = ReplaceGlobalDefines::new(&allocator, config.clone()).build(scoping, &mut program); + let mut scoping = SemanticBuilder::new().build(&program).semantic.into_scoping(); + let ret = ReplaceGlobalDefines::new(&allocator, config.clone()).build(scoping, &mut program); + // Use the updated scoping, instead of recreating one. + scoping = ret.scoping; + AssertAst.visit_program(&program); // Run DCE, to align pipeline in crates/oxc/src/compiler.rs - let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping(); Compressor::new(&allocator).dead_code_elimination_with_scoping( &mut program, scoping, @@ -37,6 +40,14 @@ fn test_same(source_text: &str, config: &ReplaceGlobalDefinesConfig) { test(source_text, source_text, config); } +struct AssertAst; + +impl Visit<'_> for AssertAst { + fn visit_identifier_reference(&mut self, ident: &oxc_ast::ast::IdentifierReference<'_>) { + assert!(ident.reference_id.get().is_some()); + } +} + fn config>(defines: &[(S, S)]) -> ReplaceGlobalDefinesConfig { ReplaceGlobalDefinesConfig::new(defines).unwrap() }