diff --git a/crates/oxc_transformer/src/plugins/inject_global_variables.rs b/crates/oxc_transformer/src/plugins/inject_global_variables.rs index 0b3a4b2dbd834..119f5cace87eb 100644 --- a/crates/oxc_transformer/src/plugins/inject_global_variables.rs +++ b/crates/oxc_transformer/src/plugins/inject_global_variables.rs @@ -107,6 +107,7 @@ impl<'a> From<&InjectImport> for DotDefineState<'a> { pub struct InjectGlobalVariablesReturn { pub symbols: SymbolTable, pub scopes: ScopeTree, + pub changed: bool, } /// Injects import statements for global variables. @@ -125,6 +126,8 @@ pub struct InjectGlobalVariables<'a> { /// Identifiers for which dot define replaced a member expression. replaced_dot_defines: Vec<(/* identifier of member expression */ CompactStr, /* local */ CompactStr)>, + /// If there are any semantic info changed. + changed: bool, } impl<'a> Traverse<'a> for InjectGlobalVariables<'a> { @@ -140,6 +143,7 @@ impl<'a> InjectGlobalVariables<'a> { config, dot_defines: vec![], replaced_dot_defines: vec![], + changed: false, } } @@ -184,12 +188,13 @@ impl<'a> InjectGlobalVariables<'a> { .collect::>(); if injects.is_empty() { - return InjectGlobalVariablesReturn { symbols, scopes }; + return InjectGlobalVariablesReturn { symbols, scopes, changed: self.changed }; } self.inject_imports(&injects, program); + self.changed = !injects.is_empty(); - InjectGlobalVariablesReturn { symbols, scopes } + InjectGlobalVariablesReturn { symbols, scopes, changed: self.changed } } fn inject_imports(&self, injects: &[InjectImport], program: &mut Program<'a>) { @@ -245,6 +250,7 @@ impl<'a> InjectGlobalVariables<'a> { let value = self.ast.expression_identifier_reference(SPAN, value_atom); *expr = value; + self.changed = true; break; } } diff --git a/crates/oxc_transformer/src/plugins/replace_global_defines.rs b/crates/oxc_transformer/src/plugins/replace_global_defines.rs index 333bfb10ce094..352605f63cae6 100644 --- a/crates/oxc_transformer/src/plugins/replace_global_defines.rs +++ b/crates/oxc_transformer/src/plugins/replace_global_defines.rs @@ -190,6 +190,7 @@ impl ReplaceGlobalDefinesConfig { pub struct ReplaceGlobalDefinesReturn { pub symbols: SymbolTable, pub scopes: ScopeTree, + pub changed: bool, } /// Replace Global Defines. @@ -202,6 +203,8 @@ pub struct ReplaceGlobalDefinesReturn { pub struct ReplaceGlobalDefines<'a> { allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig, + /// If there are any semantic info changed. + changed: bool, } impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> { @@ -213,7 +216,7 @@ impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> { impl<'a> ReplaceGlobalDefines<'a> { pub fn new(allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig) -> Self { - Self { allocator, config } + Self { allocator, config, changed: false } } pub fn build( @@ -223,7 +226,7 @@ impl<'a> ReplaceGlobalDefines<'a> { program: &mut Program<'a>, ) -> ReplaceGlobalDefinesReturn { let (symbols, scopes) = traverse_mut(self, self.allocator, program, symbols, scopes); - ReplaceGlobalDefinesReturn { symbols, scopes } + ReplaceGlobalDefinesReturn { symbols, scopes, changed: self.changed } } // Construct a new expression because we don't have ast clone right now. @@ -234,7 +237,7 @@ impl<'a> ReplaceGlobalDefines<'a> { Parser::new(self.allocator, source_text, SourceType::default()).parse_expression().unwrap() } - fn replace_identifier_defines(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + fn replace_identifier_defines(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let Expression::Identifier(ident) = expr else { return }; if !ident.is_global_reference(ctx.symbols()) { return; @@ -243,6 +246,7 @@ impl<'a> ReplaceGlobalDefines<'a> { if ident.name.as_str() == key { let value = self.parse_value(value); *expr = value; + self.changed = true; break; } } @@ -255,6 +259,7 @@ impl<'a> ReplaceGlobalDefines<'a> { if Self::is_dot_define(ctx.symbols(), dot_define, member) { let value = self.parse_value(&dot_define.value); *expr = value; + self.changed = true; return; } } @@ -262,6 +267,7 @@ impl<'a> ReplaceGlobalDefines<'a> { if Self::is_meta_property_define(meta_property_define, member) { let value = self.parse_value(&meta_property_define.value); *expr = value; + self.changed = true; return; } } @@ -272,6 +278,7 @@ impl<'a> ReplaceGlobalDefines<'a> { { let value = self.parse_value(replacement); *expr = value; + self.changed = true; } } } diff --git a/crates/oxc_transformer/tests/integrations/plugins/inject_global_variables.rs b/crates/oxc_transformer/tests/integrations/plugins/inject_global_variables.rs index 26ee7e9b6b0eb..b06ae9864f96f 100644 --- a/crates/oxc_transformer/tests/integrations/plugins/inject_global_variables.rs +++ b/crates/oxc_transformer/tests/integrations/plugins/inject_global_variables.rs @@ -7,28 +7,38 @@ use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; -use oxc_transformer::{InjectGlobalVariables, InjectGlobalVariablesConfig, InjectImport}; +use oxc_transformer::{ + InjectGlobalVariables, InjectGlobalVariablesConfig, InjectGlobalVariablesReturn, InjectImport, +}; use crate::codegen; -pub(crate) fn test(source_text: &str, expected: &str, config: InjectGlobalVariablesConfig) { +/// `semantic_info_changed` is used to assert that the semantic information has changed. +pub(crate) fn test( + source_text: &str, + expected: &str, + config: InjectGlobalVariablesConfig, + semantic_info_changed: bool, +) { let source_type = SourceType::default(); let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let mut program = ret.program; let (symbols, scopes) = SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); - let _ = InjectGlobalVariables::new(&allocator, config).build(symbols, scopes, &mut program); + let InjectGlobalVariablesReturn { changed, .. } = + InjectGlobalVariables::new(&allocator, config).build(symbols, scopes, &mut program); let result = CodeGenerator::new() .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) .build(&program) .code; let expected = codegen(expected, source_type); assert_eq!(result, expected, "for source {source_text}"); + assert_eq!(semantic_info_changed, changed, "for source {source_text}"); } fn test_same(source_text: &str, config: InjectGlobalVariablesConfig) { - test(source_text, source_text, config); + test(source_text, source_text, config, false); } #[test] @@ -48,6 +58,7 @@ fn default() { }); ", config, + true, ); } @@ -69,6 +80,7 @@ fn basic() { }); ", config, + true, ); // inserts a default import statement let config = @@ -86,6 +98,7 @@ fn basic() { }); "#, config, + true, ); } @@ -104,6 +117,7 @@ fn named() { Promise.all([thisThing, thatThing]).then(() => someOtherThing); ", config, + true, ); } @@ -128,6 +142,7 @@ fn keypaths() { export default clone; ", config, + true ); } @@ -191,6 +206,7 @@ polyfills.Promise.resolve().then(() => 'it works'); polyfills.Promise.resolve().then(() => 'it works'); ", config, + true, ); } @@ -253,6 +269,7 @@ fn shorthand_func_fallback() { foo(); ", config, + true, ); } @@ -270,6 +287,7 @@ fn redundant_keys() { $inject_Buffer_isBuffer('foo'); ", config.clone(), + true, ); // not found @@ -292,6 +310,7 @@ fn import_namespace() { console.log(foo.baz); ", config, + true, ); } @@ -318,18 +337,7 @@ fn is_reference() { // ignores check isReference is false let config = InjectGlobalVariablesConfig::new(vec![InjectImport::named_specifier("path", None, "bar")]); - test( - " - import { bar as foo } from 'path'; - console.log({ bar: foo }); - class Foo { - bar() { - console.log(this); - } - } - export { Foo }; - export { foo as bar }; - ", + test_same( " import { bar as foo } from 'path'; console.log({ bar: foo }); 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 87850340e99a3..60ec89eef20f4 100644 --- a/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs +++ b/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs @@ -3,34 +3,44 @@ use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; -use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; +use oxc_transformer::{ + ReplaceGlobalDefines, ReplaceGlobalDefinesConfig, ReplaceGlobalDefinesReturn, +}; use crate::codegen; -pub(crate) fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefinesConfig) { +/// `semantic_info_changed` is used to assert that the semantic information has changed. +pub(crate) fn test( + source_text: &str, + expected: &str, + config: ReplaceGlobalDefinesConfig, + expect_semantic_info_changed: bool, +) { let source_type = SourceType::default(); let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let mut program = ret.program; let (symbols, scopes) = SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); - let _ = ReplaceGlobalDefines::new(&allocator, config).build(symbols, scopes, &mut program); + let ReplaceGlobalDefinesReturn { changed, .. } = + ReplaceGlobalDefines::new(&allocator, config).build(symbols, scopes, &mut program); let result = CodeGenerator::new() .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) .build(&program) .code; let expected = codegen(expected, source_type); assert_eq!(result, expected, "for source {source_text}"); + assert_eq!(changed, expect_semantic_info_changed, "for source {source_text}"); } fn test_same(source_text: &str, config: ReplaceGlobalDefinesConfig) { - test(source_text, source_text, config); + test(source_text, source_text, config, false); } #[test] fn simple() { let config = ReplaceGlobalDefinesConfig::new(&[("id", "text"), ("str", "'text'")]).unwrap(); - test("id, str", "text, 'text'", config); + test("id, str", "text, 'text'", config, true); } #[test] @@ -50,23 +60,23 @@ fn shadowed() { fn dot() { let config = ReplaceGlobalDefinesConfig::new(&[("process.env.NODE_ENV", "production")]).unwrap(); - test("process.env.NODE_ENV", "production", config.clone()); - test("process.env", "process.env", config.clone()); - test("process.env.foo.bar", "process.env.foo.bar", config.clone()); - test("process", "process", config); + test("process.env.NODE_ENV", "production", config.clone(), true); + test("process.env", "process.env", config.clone(), false); + test("process.env.foo.bar", "process.env.foo.bar", config.clone(), false); + test("process", "process", config, false); } #[test] fn dot_nested() { let config = ReplaceGlobalDefinesConfig::new(&[("process", "production")]).unwrap(); - test("foo.process.NODE_ENV", "foo.process.NODE_ENV", config); + test("foo.process.NODE_ENV", "foo.process.NODE_ENV", config, false); } #[test] fn dot_with_postfix_wildcard() { let config = ReplaceGlobalDefinesConfig::new(&[("import.meta.env.*", "undefined")]).unwrap(); - test("import.meta.env.result", "undefined", config.clone()); - test("import.meta.env", "import.meta.env", config); + test("import.meta.env.result", "undefined", config.clone(), true); + test("import.meta.env", "import.meta.env", config, false); } #[test] @@ -78,9 +88,9 @@ fn dot_with_postfix_mixed() { ("import.meta", "1"), ]) .unwrap(); - test("import.meta.env.result", "undefined", config.clone()); - test("import.meta.env.result.many.nested", "undefined", config.clone()); - test("import.meta.env", "env", config.clone()); - test("import.meta.somethingelse", "metaProperty", config.clone()); - test("import.meta", "1", config); + test("import.meta.env.result", "undefined", config.clone(), true); + test("import.meta.env.result.many.nested", "undefined", config.clone(), true); + test("import.meta.env", "env", config.clone(), true); + test("import.meta.somethingelse", "metaProperty", config.clone(), true); + test("import.meta", "1", config, true); }