fix(oxc_transform): overlap replacement (#7621)

closed  https://github.com/oxc-project/oxc/issues/7594

### Reference
https://github.com/oxc-project/oxc/pull/7343/files
This commit is contained in:
IWANABETHATGUY 2024-12-06 15:26:53 +00:00
parent ac910eea5e
commit f7d41dd6fb
2 changed files with 70 additions and 12 deletions

View file

@ -1,7 +1,7 @@
use std::{cmp::Ordering, sync::Arc};
use lazy_static::lazy_static;
use oxc_allocator::Allocator;
use oxc_allocator::{Address, Allocator, GetAddress};
use oxc_ast::ast::*;
use oxc_diagnostics::OxcDiagnostic;
use oxc_parser::Parser;
@ -220,12 +220,31 @@ pub struct ReplaceGlobalDefinesReturn {
pub struct ReplaceGlobalDefines<'a> {
allocator: &'a Allocator,
config: ReplaceGlobalDefinesConfig,
/// Since `Traverse` did not provide a way to skipping visiting sub tree of the AstNode,
/// Use `Option<Address>` to lock the current node when it is `Some`.
/// during visiting sub tree, the `Lock` will always be `Some`, and we can early return, this
/// could acheieve same effect as skipping visiting sub tree.
/// When `exit` the node, reset the `Lock` to `None` to make sure not affect other
/// transformation.
ast_node_lock: Option<Address>,
}
impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.replace_identifier_defines(expr, ctx);
self.replace_dot_defines(expr, ctx);
if self.ast_node_lock.is_some() {
return;
}
let is_replaced =
self.replace_identifier_defines(expr, ctx) || self.replace_dot_defines(expr, ctx);
if is_replaced {
self.ast_node_lock = Some(expr.address());
}
}
fn exit_expression(&mut self, node: &mut Expression<'a>, _ctx: &mut TraverseCtx<'a>) {
if self.ast_node_lock == Some(node.address()) {
self.ast_node_lock = None;
}
}
fn enter_assignment_expression(
@ -233,13 +252,30 @@ impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> {
node: &mut AssignmentExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.replace_define_with_assignment_expr(node, ctx);
if self.ast_node_lock.is_some() {
return;
}
if self.replace_define_with_assignment_expr(node, ctx) {
// `AssignmentExpression` is stored in a `Box`, so we can use `from_ptr` to get
// the stable address
self.ast_node_lock = Some(Address::from_ptr(node));
}
}
fn exit_assignment_expression(
&mut self,
node: &mut AssignmentExpression<'a>,
_: &mut TraverseCtx<'a>,
) {
if self.ast_node_lock == Some(Address::from_ptr(node)) {
self.ast_node_lock = None;
}
}
}
impl<'a> ReplaceGlobalDefines<'a> {
pub fn new(allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig) -> Self {
Self { allocator, config }
Self { allocator, config, ast_node_lock: None }
}
pub fn build(
@ -260,11 +296,16 @@ impl<'a> ReplaceGlobalDefines<'a> {
Parser::new(self.allocator, source_text, SourceType::default()).parse_expression().unwrap()
}
fn replace_identifier_defines(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
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) {
*expr = new_expr;
return true;
}
}
Expression::ThisExpression(_)
@ -275,12 +316,14 @@ impl<'a> ReplaceGlobalDefines<'a> {
if key.as_str() == "this" {
let value = self.parse_value(value);
*expr = value;
break;
return true;
}
}
}
_ => {}
}
false
}
fn replace_identifier_define_impl(
@ -305,7 +348,7 @@ impl<'a> ReplaceGlobalDefines<'a> {
&mut self,
node: &mut AssignmentExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
) -> bool {
let new_left = node
.left
.as_simple_assignment_target_mut()
@ -324,10 +367,16 @@ impl<'a> ReplaceGlobalDefines<'a> {
.and_then(assignment_target_from_expr);
if let Some(new_left) = new_left {
node.left = new_left;
return true;
}
false
}
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
fn replace_dot_defines(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> bool {
match expr {
Expression::ChainExpression(chain) => {
let Some(new_expr) =
@ -341,18 +390,21 @@ impl<'a> ReplaceGlobalDefines<'a> {
MemberExpression::PrivateFieldExpression(_) => None,
})
else {
return;
return false;
};
*expr = new_expr;
return true;
}
Expression::StaticMemberExpression(member) => {
if let Some(new_expr) = self.replace_dot_static_member_expr(ctx, member) {
*expr = new_expr;
return true;
}
}
Expression::ComputedMemberExpression(member) => {
if let Some(new_expr) = self.replace_dot_computed_member_expr(ctx, member) {
*expr = new_expr;
return true;
}
}
Expression::MetaProperty(meta_property) => {
@ -361,11 +413,13 @@ impl<'a> ReplaceGlobalDefines<'a> {
{
let value = self.parse_value(replacement);
*expr = value;
return true;
}
}
}
_ => {}
}
false
}
fn replace_dot_computed_member_expr(

View file

@ -59,7 +59,6 @@ fn dot() {
test("process['env'].NODE_ENV", "production", config.clone());
}
#[ignore]
#[test]
fn dot_with_overlap() {
let config = ReplaceGlobalDefinesConfig::new(&[
@ -68,7 +67,12 @@ fn dot_with_overlap() {
])
.unwrap();
test("import.meta.env", "__foo__", config.clone());
test("import.meta.env.NODE_ENV", "import.meta.env.NODE_ENV", config.clone());
test("import.meta.env.FOO", "import.meta.env.FOO", config.clone());
test("import.meta.env.NODE_ENV", "__foo__.NODE_ENV", config.clone());
test("import.meta.env = 0", "__foo__ = 0", config.clone());
test("import.meta.env.NODE_ENV = 0", "__foo__.NODE_ENV = 0", config.clone());
test("import.meta.env.FOO = 0", "import.meta.env.FOO = 0", config.clone());
}
#[test]