feat(oxc_transformer): replace_global_define ThisExpression (#7443)

Test from d34e79e2a9/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:
IWANABETHATGUY 2024-11-25 04:13:52 +00:00
parent 6f161de10f
commit e9f9e8242a
5 changed files with 103 additions and 18 deletions

1
Cargo.lock generated
View file

@ -2069,6 +2069,7 @@ dependencies = [
"indexmap",
"insta",
"itoa",
"lazy_static",
"oxc-browserslist",
"oxc_allocator",
"oxc_ast",

View file

@ -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"] }

View file

@ -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),
) {

View file

@ -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)
}

View file

@ -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,
);
}