Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions crates/oxc/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
64 changes: 40 additions & 24 deletions crates/oxc_transformer_plugins/src/replace_global_defines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -288,20 +288,24 @@ 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].
let mut expr = Parser::new(self.allocator, source_text, SourceType::default())
.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) {
Expand All @@ -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;
Expand All @@ -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<Expression<'a>> {
if let Some(symbol_id) = ident
.reference_id
Expand All @@ -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);
}
}
Expand All @@ -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)
Expand All @@ -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,
})
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -428,16 +432,16 @@ impl<'a> ReplaceGlobalDefines<'a> {

fn replace_dot_computed_member_expr(
&self,
ctx: &TraverseCtx<'a>,
member: &ComputedMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
for dot_define in &self.config.0.dot {
if Self::is_dot_define(
ctx,
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);
}
}
Expand All @@ -447,22 +451,22 @@ impl<'a> ReplaceGlobalDefines<'a> {

fn replace_dot_static_member_expr(
&self,
ctx: &TraverseCtx<'a>,
member: &StaticMemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
for dot_define in &self.config.0.dot {
if Self::is_dot_define(
ctx,
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));
}
}
Expand Down Expand Up @@ -722,9 +726,21 @@ fn assignment_target_from_expr(expr: Expression) -> Option<AssignmentTarget> {
}
}

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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -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<S: AsRef<str>>(defines: &[(S, S)]) -> ReplaceGlobalDefinesConfig {
ReplaceGlobalDefinesConfig::new(defines).unwrap()
}
Expand Down
Loading