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",
|
"indexmap",
|
||||||
"insta",
|
"insta",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"lazy_static",
|
||||||
"oxc-browserslist",
|
"oxc-browserslist",
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
"oxc_ast",
|
"oxc_ast",
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ cow-utils = { workspace = true }
|
||||||
dashmap = { workspace = true }
|
dashmap = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
itoa = { workspace = true }
|
itoa = { workspace = true }
|
||||||
|
lazy_static = { workspace = true }
|
||||||
ropey = { workspace = true }
|
ropey = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ impl<'a> InjectGlobalVariables<'a> {
|
||||||
if let Expression::StaticMemberExpression(member) = expr {
|
if let Expression::StaticMemberExpression(member) = expr {
|
||||||
for DotDefineState { dot_define, value_atom } in &mut self.dot_defines {
|
for DotDefineState { dot_define, value_atom } in &mut self.dot_defines {
|
||||||
if ReplaceGlobalDefines::is_dot_define(
|
if ReplaceGlobalDefines::is_dot_define(
|
||||||
ctx.symbols(),
|
ctx,
|
||||||
dot_define,
|
dot_define,
|
||||||
DotDefineMemberExpression::StaticMemberExpression(member),
|
DotDefineMemberExpression::StaticMemberExpression(member),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use std::{cmp::Ordering, sync::Arc};
|
use std::{cmp::Ordering, sync::Arc};
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
use oxc_semantic::{IsGlobalReference, ScopeFlags, ScopeTree, SymbolTable};
|
||||||
use oxc_span::{CompactStr, SourceType};
|
use oxc_span::{CompactStr, SourceType};
|
||||||
use oxc_syntax::identifier::is_identifier_name;
|
use oxc_syntax::identifier::is_identifier_name;
|
||||||
use oxc_traverse::{traverse_mut, Ancestor, Traverse, TraverseCtx};
|
use oxc_traverse::{traverse_mut, Ancestor, Traverse, TraverseCtx};
|
||||||
|
|
@ -19,9 +20,19 @@ use rustc_hash::FxHashSet;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ReplaceGlobalDefinesConfig(Arc<ReplaceGlobalDefinesConfigImpl>);
|
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)]
|
#[derive(Debug)]
|
||||||
struct ReplaceGlobalDefinesConfigImpl {
|
struct ReplaceGlobalDefinesConfigImpl {
|
||||||
identifier: Vec<(/* key */ CompactStr, /* value */ CompactStr)>,
|
identifier: IdentifierDefine,
|
||||||
dot: Vec<DotDefine>,
|
dot: Vec<DotDefine>,
|
||||||
meta_property: Vec<MetaPropertyDefine>,
|
meta_property: Vec<MetaPropertyDefine>,
|
||||||
/// extra field to avoid linear scan `meta_property` to check if it has `import.meta` every
|
/// 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 dot_defines = vec![];
|
||||||
let mut meta_properties_defines = vec![];
|
let mut meta_properties_defines = vec![];
|
||||||
let mut import_meta = None;
|
let mut import_meta = None;
|
||||||
|
let mut has_this_expr_define = false;
|
||||||
for (key, value) in defines {
|
for (key, value) in defines {
|
||||||
let key = key.as_ref();
|
let key = key.as_ref();
|
||||||
|
|
||||||
|
|
@ -86,6 +98,7 @@ impl ReplaceGlobalDefinesConfig {
|
||||||
|
|
||||||
match Self::check_key(key)? {
|
match Self::check_key(key)? {
|
||||||
IdentifierType::Identifier => {
|
IdentifierType::Identifier => {
|
||||||
|
has_this_expr_define |= key == "this";
|
||||||
identifier_defines.push((CompactStr::new(key), CompactStr::new(value)));
|
identifier_defines.push((CompactStr::new(key), CompactStr::new(value)));
|
||||||
}
|
}
|
||||||
IdentifierType::DotDefines { parts } => {
|
IdentifierType::DotDefines { parts } => {
|
||||||
|
|
@ -124,7 +137,7 @@ impl ReplaceGlobalDefinesConfig {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl {
|
Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl {
|
||||||
identifier: identifier_defines,
|
identifier: IdentifierDefine { identifier_defines, has_this_expr_define },
|
||||||
dot: dot_defines,
|
dot: dot_defines,
|
||||||
meta_property: meta_properties_defines,
|
meta_property: meta_properties_defines,
|
||||||
import_meta,
|
import_meta,
|
||||||
|
|
@ -240,16 +253,33 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
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>) {
|
||||||
let Expression::Identifier(ident) = expr else { return };
|
match expr {
|
||||||
if !ident.is_global_reference(ctx.symbols()) {
|
Expression::Identifier(ident) => {
|
||||||
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);
|
for (key, value) in &self.config.0.identifier.identifier_defines {
|
||||||
*expr = value;
|
if ident.name.as_str() == key {
|
||||||
break;
|
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>> {
|
) -> Option<Expression<'a>> {
|
||||||
for dot_define in &self.config.0.dot {
|
for dot_define in &self.config.0.dot {
|
||||||
if Self::is_dot_define(
|
if Self::is_dot_define(
|
||||||
ctx.symbols(),
|
ctx,
|
||||||
dot_define,
|
dot_define,
|
||||||
DotDefineMemberExpression::ComputedMemberExpression(member),
|
DotDefineMemberExpression::ComputedMemberExpression(member),
|
||||||
) {
|
) {
|
||||||
|
|
@ -320,7 +350,7 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
for dot_define in &self.config.0.dot {
|
for dot_define in &self.config.0.dot {
|
||||||
if Self::is_dot_define(
|
if Self::is_dot_define(
|
||||||
ctx.symbols(),
|
ctx,
|
||||||
dot_define,
|
dot_define,
|
||||||
DotDefineMemberExpression::StaticMemberExpression(member),
|
DotDefineMemberExpression::StaticMemberExpression(member),
|
||||||
) {
|
) {
|
||||||
|
|
@ -415,12 +445,12 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_dot_define<'b>(
|
pub fn is_dot_define<'b>(
|
||||||
symbols: &SymbolTable,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
dot_define: &DotDefine,
|
dot_define: &DotDefine,
|
||||||
member: DotDefineMemberExpression<'b, 'a>,
|
member: DotDefineMemberExpression<'b, 'a>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
debug_assert!(dot_define.parts.len() > 1);
|
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 {
|
let Some(mut cur_part_name) = member.name() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
@ -447,12 +477,16 @@ impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
if !ident.is_global_reference(symbols) {
|
if !ident.is_global_reference(ctx.symbols()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
cur_part_name = &ident.name;
|
cur_part_name = &ident.name;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
Expression::ThisExpression(_) if should_replace_this_expr => {
|
||||||
|
cur_part_name = &THIS_ATOM;
|
||||||
|
None
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -557,3 +591,7 @@ fn destructing_dot_define_optimizer<'ast>(
|
||||||
obj.properties.retain(|_| *iter.next().unwrap());
|
obj.properties.retain(|_| *iter.next().unwrap());
|
||||||
expr
|
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(),
|
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