From 10477a4f3e757188a04ddcf1fe7500dd085e464c Mon Sep 17 00:00:00 2001 From: IWANABETHATGUY Date: Sun, 24 Nov 2024 00:16:25 +0800 Subject: [PATCH] feat: replace_global_define this expr --- Cargo.lock | 1 + crates/oxc_transformer/Cargo.toml | 1 + .../src/plugins/inject_global_variables.rs | 2 +- .../src/plugins/replace_global_defines.rs | 72 ++++++++++++++----- .../plugins/replace_global_defines.rs | 45 ++++++++++++ 5 files changed, 103 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88df345f6ea362..ec4ede77370ec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2069,6 +2069,7 @@ dependencies = [ "indexmap", "insta", "itoa", + "lazy_static", "oxc-browserslist", "oxc_allocator", "oxc_ast", diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index 5f7ff9698e8e0f..07694ca3fe3bd9 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -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"] } diff --git a/crates/oxc_transformer/src/plugins/inject_global_variables.rs b/crates/oxc_transformer/src/plugins/inject_global_variables.rs index 7fe01c47ec3248..d45e07ab015cff 100644 --- a/crates/oxc_transformer/src/plugins/inject_global_variables.rs +++ b/crates/oxc_transformer/src/plugins/inject_global_variables.rs @@ -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), ) { diff --git a/crates/oxc_transformer/src/plugins/replace_global_defines.rs b/crates/oxc_transformer/src/plugins/replace_global_defines.rs index 72f08f963bfa6c..11af007e7552f0 100644 --- a/crates/oxc_transformer/src/plugins/replace_global_defines.rs +++ b/crates/oxc_transformer/src/plugins/replace_global_defines.rs @@ -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, Traverse, TraverseCtx}; @@ -18,9 +19,19 @@ use oxc_traverse::{traverse_mut, Traverse, TraverseCtx}; #[derive(Debug, Clone)] pub struct ReplaceGlobalDefinesConfig(Arc); +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, meta_property: Vec, /// extra field to avoid linear scan `meta_property` to check if it has `import.meta` every @@ -77,6 +88,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(); @@ -85,6 +97,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 } => { @@ -123,7 +136,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, @@ -239,16 +252,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; + } + } } + _ => {} } } @@ -300,7 +330,7 @@ impl<'a> ReplaceGlobalDefines<'a> { ) -> Option> { for dot_define in &self.config.0.dot { if Self::is_dot_define( - ctx.symbols(), + ctx, dot_define, DotDefineMemberExpression::ComputedMemberExpression(member), ) { @@ -319,7 +349,7 @@ impl<'a> ReplaceGlobalDefines<'a> { ) -> Option> { for dot_define in &self.config.0.dot { if Self::is_dot_define( - ctx.symbols(), + ctx, dot_define, DotDefineMemberExpression::StaticMemberExpression(member), ) { @@ -414,12 +444,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; }; @@ -446,12 +476,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 { @@ -498,3 +532,7 @@ fn static_property_name_of_computed_expr<'b, 'a: 'b>( _ => None, } } + +const fn should_replace_this_expr(scope_flags: ScopeFlags) -> bool { + !scope_flags.contains(ScopeFlags::Function) || scope_flags.contains(ScopeFlags::Arrow) +} diff --git a/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs b/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs index 84ac9bbf8d5bf0..b7d96491419a7c 100644 --- a/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs +++ b/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs @@ -104,3 +104,48 @@ fn optional_chain() { test_same("a?.[b][c]", config.clone()); test_same("a[b]?.[c]", 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, + ); +}