Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions crates/oxc_transformer/src/plugins/inject_global_variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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> {
Expand All @@ -140,6 +143,7 @@ impl<'a> InjectGlobalVariables<'a> {
config,
dot_defines: vec![],
replaced_dot_defines: vec![],
changed: false,
}
}

Expand Down Expand Up @@ -184,12 +188,13 @@ impl<'a> InjectGlobalVariables<'a> {
.collect::<Vec<_>>();

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>) {
Expand Down Expand Up @@ -245,6 +250,7 @@ impl<'a> InjectGlobalVariables<'a> {

let value = self.ast.expression_identifier_reference(SPAN, value_atom);
*expr = value;
self.changed = true;
break;
}
}
Expand Down
13 changes: 10 additions & 3 deletions crates/oxc_transformer/src/plugins/replace_global_defines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ impl ReplaceGlobalDefinesConfig {
pub struct ReplaceGlobalDefinesReturn {
pub symbols: SymbolTable,
pub scopes: ScopeTree,
pub changed: bool,
}

/// Replace Global Defines.
Expand All @@ -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> {
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -255,13 +259,15 @@ 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;
}
}
for meta_property_define in &self.config.0.meta_property {
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;
}
}
Expand All @@ -272,6 +278,7 @@ impl<'a> ReplaceGlobalDefines<'a> {
{
let value = self.parse_value(replacement);
*expr = value;
self.changed = true;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -48,6 +58,7 @@ fn default() {
});
",
config,
true,
);
}

Expand All @@ -69,6 +80,7 @@ fn basic() {
});
",
config,
true,
);
// inserts a default import statement
let config =
Expand All @@ -86,6 +98,7 @@ fn basic() {
});
"#,
config,
true,
);
}

Expand All @@ -104,6 +117,7 @@ fn named() {
Promise.all([thisThing, thatThing]).then(() => someOtherThing);
",
config,
true,
);
}

Expand All @@ -128,6 +142,7 @@ fn keypaths() {
export default clone;
",
config,
true
);
}

Expand Down Expand Up @@ -191,6 +206,7 @@ polyfills.Promise.resolve().then(() => 'it works');
polyfills.Promise.resolve().then(() => 'it works');
",
config,
true,
);
}

Expand Down Expand Up @@ -253,6 +269,7 @@ fn shorthand_func_fallback() {
foo();
",
config,
true,
);
}

Expand All @@ -270,6 +287,7 @@ fn redundant_keys() {
$inject_Buffer_isBuffer('foo');
",
config.clone(),
true,
);

// not found
Expand All @@ -292,6 +310,7 @@ fn import_namespace() {
console.log(foo.baz);
",
config,
true,
);
}

Expand All @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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);
}