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", "indexmap",
"insta", "insta",
"itoa", "itoa",
"lazy_static",
"oxc-browserslist", "oxc-browserslist",
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",

View file

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

View file

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

View file

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

View file

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