Skip to content

Commit b172cef

Browse files
committed
fix(transformer_plugins/replace_global_defines): handle declare const (#12312)
`declare const IS_PROD: boolean; if (IS_PROD) {}` -> `declare const IS_PROD: boolean; {}` fixes rolldown/rolldown#5251
1 parent a257c01 commit b172cef

File tree

2 files changed

+56
-63
lines changed

2 files changed

+56
-63
lines changed

crates/oxc_transformer_plugins/src/replace_global_defines.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,17 @@ impl<'a> ReplaceGlobalDefines<'a> {
332332
ident: &oxc_allocator::Box<'_, IdentifierReference<'_>>,
333333
ctx: &TraverseCtx<'a>,
334334
) -> Option<Expression<'a>> {
335-
if !ident.is_global_reference(ctx.scoping()) {
336-
return None;
335+
if let Some(symbol_id) = ident
336+
.reference_id
337+
.get()
338+
.and_then(|reference_id| ctx.scoping().get_reference(reference_id).symbol_id())
339+
{
340+
// Ignore `declare const IS_PROD: boolean;`
341+
if !ctx.scoping().symbol_flags(symbol_id).is_ambient() {
342+
return None;
343+
}
337344
}
345+
// This is a global variable, including ambient variants such as `declare const`.
338346
for (key, value) in &self.config.0.identifier.identifier_defines {
339347
if ident.name.as_str() == key {
340348
let value = self.parse_value(value);

crates/oxc_transformer_plugins/tests/integrations/replace_global_defines.rs

Lines changed: 46 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ use crate::codegen;
1010

1111
#[track_caller]
1212
pub fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefinesConfig) {
13-
let source_type = SourceType::default();
13+
let source_type = SourceType::ts();
1414
let allocator = Allocator::default();
1515
let ret = Parser::new(&allocator, source_text, source_type).parse();
16+
assert!(ret.errors.is_empty());
1617
let mut program = ret.program;
1718
let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping();
1819
let _ = ReplaceGlobalDefines::new(&allocator, config).build(scoping, &mut program);
@@ -33,29 +34,28 @@ fn test_same(source_text: &str, config: ReplaceGlobalDefinesConfig) {
3334
test(source_text, source_text, config);
3435
}
3536

37+
fn config<S: AsRef<str>>(defines: &[(S, S)]) -> ReplaceGlobalDefinesConfig {
38+
ReplaceGlobalDefinesConfig::new(defines).unwrap()
39+
}
40+
3641
#[test]
3742
fn simple() {
38-
let config = ReplaceGlobalDefinesConfig::new(&[("id", "text"), ("str", "'text'")]).unwrap();
43+
let config = config(&[("id", "text"), ("str", "'text'")]);
3944
test("const _ = [id, str]", "const _ = [text, 'text']", config);
4045
}
4146

4247
#[test]
4348
fn shadowed() {
44-
let config = ReplaceGlobalDefinesConfig::new(&[
45-
("undefined", "text"),
46-
("NaN", "'text'"),
47-
("process.env.NODE_ENV", "'test'"),
48-
])
49-
.unwrap();
49+
let config =
50+
config(&[("undefined", "text"), ("NaN", "'text'"), ("process.env.NODE_ENV", "'test'")]);
5051
test_same("(function (undefined) { let x = typeof undefined })()", config.clone());
5152
test_same("(function (NaN) { let x = typeof NaN })()", config.clone());
5253
test_same("(function (process) { let x = process.env.NODE_ENV })()", config);
5354
}
5455

5556
#[test]
5657
fn dot() {
57-
let config =
58-
ReplaceGlobalDefinesConfig::new(&[("process.env.NODE_ENV", "production")]).unwrap();
58+
let config = config(&[("process.env.NODE_ENV", "production")]);
5959
test("const _ = process.env.NODE_ENV", "const _ = production", config.clone());
6060
test("const _ = process.env", "const _ = process.env", config.clone());
6161
test("const _ = process.env.foo.bar", "const _ = process.env.foo.bar", config.clone());
@@ -67,11 +67,8 @@ fn dot() {
6767

6868
#[test]
6969
fn dot_with_overlap() {
70-
let config = ReplaceGlobalDefinesConfig::new(&[
71-
("import.meta.env.FOO", "import.meta.env.FOO"),
72-
("import.meta.env", "__foo__"),
73-
])
74-
.unwrap();
70+
let config =
71+
config(&[("import.meta.env.FOO", "import.meta.env.FOO"), ("import.meta.env", "__foo__")]);
7572
test("const _ = import.meta.env", "const _ = __foo__", config.clone());
7673
test("const _ = import.meta.env.FOO", "const _ = import.meta.env.FOO", config.clone());
7774
test("const _ = import.meta.env.NODE_ENV", "const _ = __foo__.NODE_ENV", config.clone());
@@ -83,11 +80,10 @@ fn dot_with_overlap() {
8380

8481
#[test]
8582
fn dot_define_is_member_expr_postfix() {
86-
let config = ReplaceGlobalDefinesConfig::new(&[
83+
let config = config(&[
8784
("__OBJ__", r#"{"process":{"env":{"SOMEVAR":"foo"}}}"#),
8885
("process.env.SOMEVAR", "\"SOMEVAR\""),
89-
])
90-
.unwrap();
86+
]);
9187
test(
9288
"console.log(__OBJ__.process.env.SOMEVAR)",
9389
"console.log({ 'process': { 'env': { 'SOMEVAR': 'foo' } } }.process.env.SOMEVAR);\n",
@@ -97,28 +93,27 @@ fn dot_define_is_member_expr_postfix() {
9793

9894
#[test]
9995
fn dot_nested() {
100-
let config = ReplaceGlobalDefinesConfig::new(&[("process", "production")]).unwrap();
96+
let config = config(&[("process", "production")]);
10197
test("foo.process.NODE_ENV", "foo.process.NODE_ENV", config.clone());
10298
// computed member expression
10399
test("foo['process'].NODE_ENV", "foo['process'].NODE_ENV", config);
104100
}
105101

106102
#[test]
107103
fn dot_with_postfix_wildcard() {
108-
let config = ReplaceGlobalDefinesConfig::new(&[("import.meta.env.*", "undefined")]).unwrap();
104+
let config = config(&[("import.meta.env.*", "undefined")]);
109105
test("const _ = import.meta.env.result", "const _ = void 0", config.clone());
110106
test("const _ = import.meta.env", "const _ = import.meta.env", config);
111107
}
112108

113109
#[test]
114110
fn dot_with_postfix_mixed() {
115-
let config = ReplaceGlobalDefinesConfig::new(&[
111+
let config = config(&[
116112
("import.meta.env.*", "undefined"),
117113
("import.meta.env", "env"),
118114
("import.meta.*", "metaProperty"),
119115
("import.meta", "1"),
120-
])
121-
.unwrap();
116+
]);
122117
test("const _ = import.meta.env.result", "const _ = void 0", config.clone());
123118
test("const _ = import.meta.env.result.many.nested", "const _ = void 0", config.clone());
124119
test("const _ = import.meta.env", "const _ = env", config.clone());
@@ -128,7 +123,7 @@ fn dot_with_postfix_mixed() {
128123

129124
#[test]
130125
fn optional_chain() {
131-
let config = ReplaceGlobalDefinesConfig::new(&[("a.b.c", "1")]).unwrap();
126+
let config = config(&[("a.b.c", "1")]);
132127
test("const _ = a.b.c", "const _ = 1", config.clone());
133128
test("const _ = a?.b.c", "const _ = 1", config.clone());
134129
test("const _ = a.b?.c", "const _ = 1", config.clone());
@@ -143,45 +138,31 @@ fn optional_chain() {
143138

144139
#[test]
145140
fn dot_define_with_destruct() {
146-
let config = ReplaceGlobalDefinesConfig::new(&[(
147-
"process.env.NODE_ENV",
148-
"{'a': 1, b: 2, c: true, d: {a: b}}",
149-
)])
150-
.unwrap();
141+
let c = config(&[("process.env.NODE_ENV", "{'a': 1, b: 2, c: true, d: {a: b}}")]);
151142
test(
152143
"const {a, c} = process.env.NODE_ENV",
153144
"const { a, c } = {\n\t'a': 1,\n\tc: true};",
154-
config.clone(),
145+
c.clone(),
155146
);
156147
// bailout
157148
test(
158149
"const {[any]: alias} = process.env.NODE_ENV",
159150
"const { [any]: alias } = {\n\t'a': 1,\n\tb: 2,\n\tc: true,\n\td: { a: b }\n};",
160-
config,
151+
c,
161152
);
162153

163154
// should filterout unused key even rhs objectExpr has SpreadElement
164155

165-
let config = ReplaceGlobalDefinesConfig::new(&[(
166-
"process.env.NODE_ENV",
167-
"{'a': 1, b: 2, c: true, ...unknown}",
168-
)])
169-
.unwrap();
170-
test(
171-
"const {a} = process.env.NODE_ENV",
172-
"const { a } = {\n\t'a': 1,\n\t...unknown\n};\n",
173-
config,
174-
);
156+
let c = config(&[("process.env.NODE_ENV", "{'a': 1, b: 2, c: true, ...unknown}")]);
157+
test("const {a} = process.env.NODE_ENV", "const { a } = {\n\t'a': 1,\n\t...unknown\n};\n", c);
175158
}
176159

177160
#[test]
178161
fn this_expr() {
179-
let config =
180-
ReplaceGlobalDefinesConfig::new(&[("this", "1"), ("this.foo", "2"), ("this.foo.bar", "3")])
181-
.unwrap();
162+
let config = config(&[("this", "1"), ("this.foo", "2"), ("this.foo.bar", "3")]);
182163
test(
183-
"const _ = this, this.foo, this.foo.bar, this.foo.baz, this.bar",
184-
"const _ = 1, 2, 3, 2 .baz, 1 .bar;\n",
164+
"const _ = this; this.foo, this.foo.bar, this.foo.baz, this.bar",
165+
"const _ = 1; 2 .baz, 1 .bar;",
185166
config.clone(),
186167
);
187168

@@ -215,13 +196,8 @@ fn this_expr() {
215196

216197
#[test]
217198
fn assignment_target() {
218-
let config = ReplaceGlobalDefinesConfig::new(&[
219-
("d", "ident"),
220-
("e.f", "ident"),
221-
("g", "dot.chain"),
222-
("h.i", "dot.chain"),
223-
])
224-
.unwrap();
199+
let config =
200+
config(&[("d", "ident"), ("e.f", "ident"), ("g", "dot.chain"), ("h.i", "dot.chain")]);
225201

226202
test(
227203
r"
@@ -238,24 +214,33 @@ console.log(
238214

239215
#[test]
240216
fn replace_with_undefined() {
241-
let config = ReplaceGlobalDefinesConfig::new(&[("Foo", "undefined")]).unwrap();
242-
test("new Foo()", "new (void 0)()", config);
217+
let c = config(&[("Foo", "undefined")]);
218+
test("new Foo()", "new (void 0)()", c);
219+
220+
let c = config(&[("Foo", "Bar")]);
221+
test("Foo = 0", "Bar = 0", c);
222+
}
243223

244-
let config = ReplaceGlobalDefinesConfig::new(&[("Foo", "Bar")]).unwrap();
245-
test("Foo = 0", "Bar = 0", config);
224+
#[test]
225+
fn declare_const() {
226+
let config = config(&[("IS_PROD", "true")]);
227+
test(
228+
"declare const IS_PROD: boolean; if (IS_PROD) {}",
229+
"declare const IS_PROD: boolean; {}",
230+
config,
231+
);
246232
}
247233

248234
#[cfg(not(miri))]
249235
#[test]
250236
fn test_sourcemap() {
251237
use oxc_sourcemap::SourcemapVisualizer;
252238

253-
let config = ReplaceGlobalDefinesConfig::new(&[
239+
let c = config(&[
254240
("__OBJECT__", r#"{"hello": "test"}"#),
255241
("__STRING__", r#""development""#),
256242
("__MEMBER__", r"xx.yy.zz"),
257-
])
258-
.unwrap();
243+
]);
259244
let source_text = r"
260245
1;
261246
__OBJECT__;
@@ -278,7 +263,7 @@ log(__MEMBER__);
278263
let ret = Parser::new(&allocator, source_text, source_type).parse();
279264
let mut program = ret.program;
280265
let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping();
281-
let _ = ReplaceGlobalDefines::new(&allocator, config).build(scoping, &mut program);
266+
let _ = ReplaceGlobalDefines::new(&allocator, c).build(scoping, &mut program);
282267
let result = Codegen::new()
283268
.with_options(CodegenOptions {
284269
single_quote: true,

0 commit comments

Comments
 (0)