mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(oxc_transformer): replace_global_define ThisExpression (#7443)
Test fromd34e79e2a9/internal/bundler_tests/bundler_default_test.go (L5195-L5260), esbuild snapshot:d34e79e2a9/internal/bundler_tests/snapshots/snapshots_default.txt (L1081-L1108)
This commit is contained in:
parent
6f161de10f
commit
e9f9e8242a
5 changed files with 103 additions and 18 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2069,6 +2069,7 @@ dependencies = [
|
|||
"indexmap",
|
||||
"insta",
|
||||
"itoa",
|
||||
"lazy_static",
|
||||
"oxc-browserslist",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ cow-utils = { workspace = true }
|
|||
dashmap = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
itoa = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
ropey = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ impl<'a> InjectGlobalVariables<'a> {
|
|||
if let Expression::StaticMemberExpression(member) = expr {
|
||||
for DotDefineState { dot_define, value_atom } in &mut self.dot_defines {
|
||||
if ReplaceGlobalDefines::is_dot_define(
|
||||
ctx.symbols(),
|
||||
ctx,
|
||||
dot_define,
|
||||
DotDefineMemberExpression::StaticMemberExpression(member),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use std::{cmp::Ordering, sync::Arc};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
||||
use oxc_semantic::{IsGlobalReference, ScopeFlags, ScopeTree, SymbolTable};
|
||||
use oxc_span::{CompactStr, SourceType};
|
||||
use oxc_syntax::identifier::is_identifier_name;
|
||||
use oxc_traverse::{traverse_mut, Ancestor, Traverse, TraverseCtx};
|
||||
|
|
@ -19,9 +20,19 @@ use rustc_hash::FxHashSet;
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct ReplaceGlobalDefinesConfig(Arc<ReplaceGlobalDefinesConfigImpl>);
|
||||
|
||||
lazy_static! {
|
||||
static ref THIS_ATOM: Atom<'static> = Atom::from("this");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct IdentifierDefine {
|
||||
identifier_defines: Vec<(/* key */ CompactStr, /* value */ CompactStr)>,
|
||||
/// Whether user want to replace `ThisExpression`, avoid linear scan for each `ThisExpression`
|
||||
has_this_expr_define: bool,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct ReplaceGlobalDefinesConfigImpl {
|
||||
identifier: Vec<(/* key */ CompactStr, /* value */ CompactStr)>,
|
||||
identifier: IdentifierDefine,
|
||||
dot: Vec<DotDefine>,
|
||||
meta_property: Vec<MetaPropertyDefine>,
|
||||
/// extra field to avoid linear scan `meta_property` to check if it has `import.meta` every
|
||||
|
|
@ -78,6 +89,7 @@ impl ReplaceGlobalDefinesConfig {
|
|||
let mut dot_defines = vec![];
|
||||
let mut meta_properties_defines = vec![];
|
||||
let mut import_meta = None;
|
||||
let mut has_this_expr_define = false;
|
||||
for (key, value) in defines {
|
||||
let key = key.as_ref();
|
||||
|
||||
|
|
@ -86,6 +98,7 @@ impl ReplaceGlobalDefinesConfig {
|
|||
|
||||
match Self::check_key(key)? {
|
||||
IdentifierType::Identifier => {
|
||||
has_this_expr_define |= key == "this";
|
||||
identifier_defines.push((CompactStr::new(key), CompactStr::new(value)));
|
||||
}
|
||||
IdentifierType::DotDefines { parts } => {
|
||||
|
|
@ -124,7 +137,7 @@ impl ReplaceGlobalDefinesConfig {
|
|||
}
|
||||
});
|
||||
Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl {
|
||||
identifier: identifier_defines,
|
||||
identifier: IdentifierDefine { identifier_defines, has_this_expr_define },
|
||||
dot: dot_defines,
|
||||
meta_property: meta_properties_defines,
|
||||
import_meta,
|
||||
|
|
@ -240,16 +253,33 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
|||
}
|
||||
|
||||
fn replace_identifier_defines(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::Identifier(ident) = expr else { return };
|
||||
if !ident.is_global_reference(ctx.symbols()) {
|
||||
return;
|
||||
}
|
||||
for (key, value) in &self.config.0.identifier {
|
||||
if ident.name.as_str() == key {
|
||||
let value = self.parse_value(value);
|
||||
*expr = value;
|
||||
break;
|
||||
match expr {
|
||||
Expression::Identifier(ident) => {
|
||||
if !ident.is_global_reference(ctx.symbols()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (key, value) in &self.config.0.identifier.identifier_defines {
|
||||
if ident.name.as_str() == key {
|
||||
let value = self.parse_value(value);
|
||||
*expr = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::ThisExpression(_)
|
||||
if self.config.0.identifier.has_this_expr_define
|
||||
&& should_replace_this_expr(ctx.current_scope_flags()) =>
|
||||
{
|
||||
for (key, value) in &self.config.0.identifier.identifier_defines {
|
||||
if key.as_str() == "this" {
|
||||
let value = self.parse_value(value);
|
||||
*expr = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +331,7 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
|||
) -> Option<Expression<'a>> {
|
||||
for dot_define in &self.config.0.dot {
|
||||
if Self::is_dot_define(
|
||||
ctx.symbols(),
|
||||
ctx,
|
||||
dot_define,
|
||||
DotDefineMemberExpression::ComputedMemberExpression(member),
|
||||
) {
|
||||
|
|
@ -320,7 +350,7 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
|||
) -> Option<Expression<'a>> {
|
||||
for dot_define in &self.config.0.dot {
|
||||
if Self::is_dot_define(
|
||||
ctx.symbols(),
|
||||
ctx,
|
||||
dot_define,
|
||||
DotDefineMemberExpression::StaticMemberExpression(member),
|
||||
) {
|
||||
|
|
@ -415,12 +445,12 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
|||
}
|
||||
|
||||
pub fn is_dot_define<'b>(
|
||||
symbols: &SymbolTable,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
dot_define: &DotDefine,
|
||||
member: DotDefineMemberExpression<'b, 'a>,
|
||||
) -> bool {
|
||||
debug_assert!(dot_define.parts.len() > 1);
|
||||
|
||||
let should_replace_this_expr = should_replace_this_expr(ctx.current_scope_flags());
|
||||
let Some(mut cur_part_name) = member.name() else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -447,12 +477,16 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
|||
})
|
||||
}
|
||||
Expression::Identifier(ident) => {
|
||||
if !ident.is_global_reference(symbols) {
|
||||
if !ident.is_global_reference(ctx.symbols()) {
|
||||
return false;
|
||||
}
|
||||
cur_part_name = &ident.name;
|
||||
None
|
||||
}
|
||||
Expression::ThisExpression(_) if should_replace_this_expr => {
|
||||
cur_part_name = &THIS_ATOM;
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -557,3 +591,7 @@ fn destructing_dot_define_optimizer<'ast>(
|
|||
obj.properties.retain(|_| *iter.next().unwrap());
|
||||
expr
|
||||
}
|
||||
|
||||
const fn should_replace_this_expr(scope_flags: ScopeFlags) -> bool {
|
||||
!scope_flags.contains(ScopeFlags::Function) || scope_flags.contains(ScopeFlags::Arrow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,3 +137,48 @@ fn dot_define_with_destruct() {
|
|||
config.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn this_expr() {
|
||||
let config =
|
||||
ReplaceGlobalDefinesConfig::new(&[("this", "1"), ("this.foo", "2"), ("this.foo.bar", "3")])
|
||||
.unwrap();
|
||||
test(
|
||||
"this, this.foo, this.foo.bar, this.foo.baz, this.bar",
|
||||
"1, 2, 3, 2 .baz, 1 .bar;\n",
|
||||
config.clone(),
|
||||
);
|
||||
|
||||
test(
|
||||
r"
|
||||
// This code should be the same as above
|
||||
(() => {
|
||||
ok(
|
||||
this,
|
||||
this.foo,
|
||||
this.foo.bar,
|
||||
this.foo.baz,
|
||||
this.bar,
|
||||
);
|
||||
})();
|
||||
",
|
||||
"(() => {\n\tok(1, 2, 3, 2 .baz, 1 .bar);\n})();\n",
|
||||
config.clone(),
|
||||
);
|
||||
|
||||
test_same(
|
||||
r"
|
||||
// Nothing should be substituted in this code
|
||||
(function() {
|
||||
doNotSubstitute(
|
||||
this,
|
||||
this.foo,
|
||||
this.foo.bar,
|
||||
this.foo.baz,
|
||||
this.bar,
|
||||
);
|
||||
})();
|
||||
",
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue