mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
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:
parent
ac910eea5e
commit
f7d41dd6fb
2 changed files with 70 additions and 12 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue