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 std::{cmp::Ordering, sync::Arc};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::{Address, Allocator, GetAddress};
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
|
|
@ -220,12 +220,31 @@ pub struct ReplaceGlobalDefinesReturn {
|
||||||
pub struct ReplaceGlobalDefines<'a> {
|
pub struct ReplaceGlobalDefines<'a> {
|
||||||
allocator: &'a Allocator,
|
allocator: &'a Allocator,
|
||||||
config: ReplaceGlobalDefinesConfig,
|
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> {
|
impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> {
|
||||||
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.replace_identifier_defines(expr, ctx);
|
if self.ast_node_lock.is_some() {
|
||||||
self.replace_dot_defines(expr, ctx);
|
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(
|
fn enter_assignment_expression(
|
||||||
|
|
@ -233,13 +252,30 @@ impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> {
|
||||||
node: &mut AssignmentExpression<'a>,
|
node: &mut AssignmentExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'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> {
|
impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
pub fn new(allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig) -> Self {
|
pub fn new(allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig) -> Self {
|
||||||
Self { allocator, config }
|
Self { allocator, config, ast_node_lock: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(
|
pub fn build(
|
||||||
|
|
@ -260,11 +296,16 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
Parser::new(self.allocator, source_text, SourceType::default()).parse_expression().unwrap()
|
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 {
|
match expr {
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
if let Some(new_expr) = self.replace_identifier_define_impl(ident, ctx) {
|
if let Some(new_expr) = self.replace_identifier_define_impl(ident, ctx) {
|
||||||
*expr = new_expr;
|
*expr = new_expr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::ThisExpression(_)
|
Expression::ThisExpression(_)
|
||||||
|
|
@ -275,12 +316,14 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
if key.as_str() == "this" {
|
if key.as_str() == "this" {
|
||||||
let value = self.parse_value(value);
|
let value = self.parse_value(value);
|
||||||
*expr = value;
|
*expr = value;
|
||||||
break;
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_identifier_define_impl(
|
fn replace_identifier_define_impl(
|
||||||
|
|
@ -305,7 +348,7 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
node: &mut AssignmentExpression<'a>,
|
node: &mut AssignmentExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) {
|
) -> bool {
|
||||||
let new_left = node
|
let new_left = node
|
||||||
.left
|
.left
|
||||||
.as_simple_assignment_target_mut()
|
.as_simple_assignment_target_mut()
|
||||||
|
|
@ -324,10 +367,16 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
.and_then(assignment_target_from_expr);
|
.and_then(assignment_target_from_expr);
|
||||||
if let Some(new_left) = new_left {
|
if let Some(new_left) = new_left {
|
||||||
node.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 {
|
match expr {
|
||||||
Expression::ChainExpression(chain) => {
|
Expression::ChainExpression(chain) => {
|
||||||
let Some(new_expr) =
|
let Some(new_expr) =
|
||||||
|
|
@ -341,18 +390,21 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
MemberExpression::PrivateFieldExpression(_) => None,
|
MemberExpression::PrivateFieldExpression(_) => None,
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
return;
|
return false;
|
||||||
};
|
};
|
||||||
*expr = new_expr;
|
*expr = new_expr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
Expression::StaticMemberExpression(member) => {
|
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(ctx, member) {
|
||||||
*expr = new_expr;
|
*expr = new_expr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::ComputedMemberExpression(member) => {
|
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(ctx, member) {
|
||||||
*expr = new_expr;
|
*expr = new_expr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::MetaProperty(meta_property) => {
|
Expression::MetaProperty(meta_property) => {
|
||||||
|
|
@ -361,11 +413,13 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
{
|
{
|
||||||
let value = self.parse_value(replacement);
|
let value = self.parse_value(replacement);
|
||||||
*expr = value;
|
*expr = value;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_dot_computed_member_expr(
|
fn replace_dot_computed_member_expr(
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ fn dot() {
|
||||||
test("process['env'].NODE_ENV", "production", config.clone());
|
test("process['env'].NODE_ENV", "production", config.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dot_with_overlap() {
|
fn dot_with_overlap() {
|
||||||
let config = ReplaceGlobalDefinesConfig::new(&[
|
let config = ReplaceGlobalDefinesConfig::new(&[
|
||||||
|
|
@ -68,7 +67,12 @@ fn dot_with_overlap() {
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
test("import.meta.env", "__foo__", config.clone());
|
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]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue