mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(transformer): inline const enum members through type-cast wrappers (#22500)
Closes #22444. `Phase.idle as Phase`, `Phase.idle satisfies Phase`, `Phase.idle!`, and `<Phase>Phase.idle` failed to inline under `optimizeConstEnums`. The const enum declaration was stripped while the member access was left as `Phase.idle`, producing a runtime `ReferenceError`. ## Cause The enum inliner's `enter_expression` matched only bare `StaticMemberExpression`/`ComputedMemberExpression`. `annotations.rs` strips TS-only wrappers in its own `enter_expression`, but `walk_expression` only calls `enter_expression` once per node — after the strip the inner `Phase.idle` was never revisited. ## Fix Peek through the wrappers via `Expression::get_inner_expression_mut()` (it already handles `TSAsExpression`, `TSSatisfiesExpression`, `TSNonNullExpression`, `TSTypeAssertion`, `TSInstantiationExpression`, `ParenthesizedExpression`). The outer `*expr = …` replacement then drops the wrapper along with the inlined value. ## Example ```ts const enum Phase { idle, active } class Foo { #foo = Phase.idle as Phase; } ``` Before: ```js class Foo { #foo = Phase.idle; // dangling reference } ``` After: ```js class Foo { #foo = 0; } ``` ## Verification New fixtures cover all wrapper shapes for const + regular enums: - `babel-plugin-transform-typescript/const-enum-type-cast/` - `babel-plugin-transform-typescript/optimize-enums/type-cast/`
This commit is contained in:
1 parent
c9f506e875
commit
931b7d6b97
7 files changed
+88
-3
No files matched your search
@@ -102,7 +102,11 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptEnum {
|
||||
return;
|
||||
}
|
||||
|
||||
let inlined = match expr {
|
||||
// Peek through TS-only wrappers and parens so `E.X as T`, `E.X satisfies T`,
|
||||
// `E.X!`, `<T>E.X`, `E.X` (with `preserveParens`) all inline. `annotations.rs`
|
||||
// strips these wrappers, but only after this hook returns — by then the outer
|
||||
// node has been replaced and `enter_expression` is not re-invoked on it.
|
||||
let inlined = match expr.get_inner_expression_mut() {
|
||||
Expression::StaticMemberExpression(member_expr) => {
|
||||
self.try_inline_enum_member(member_expr, ctx)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: 4079bcda
|
||||
|
||||
Passed: 235/382
|
||||
Passed: 236/384
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
@@ -63,7 +63,7 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R
|
||||
rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)]
|
||||
|
||||
|
||||
# babel-plugin-transform-typescript (22/56)
|
||||
# babel-plugin-transform-typescript (23/58)
|
||||
* allow-declare-fields-false/input.ts
|
||||
Unresolved references mismatch:
|
||||
after transform: ["dce"]
|
||||
@@ -526,6 +526,20 @@ Symbol flags mismatch for "B":
|
||||
after transform: SymbolId(2): SymbolFlags(RegularEnum)
|
||||
rebuilt : SymbolId(2): SymbolFlags(FunctionScopedVariable)
|
||||
|
||||
* optimize-enums/type-cast/input.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(1): ["Direction", "Down", "Up"]
|
||||
rebuilt : ScopeId(1): ["Direction"]
|
||||
Scope flags mismatch:
|
||||
after transform: ScopeId(1): ScopeFlags(0x0)
|
||||
rebuilt : ScopeId(1): ScopeFlags(Function)
|
||||
Symbol flags mismatch for "Direction":
|
||||
after transform: SymbolId(0): SymbolFlags(RegularEnum)
|
||||
rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable)
|
||||
Symbol reference IDs mismatch for "Direction":
|
||||
after transform: SymbolId(0): [ReferenceId(5), ReferenceId(1), ReferenceId(7), ReferenceId(3), ReferenceId(14)]
|
||||
rebuilt : SymbolId(0): [ReferenceId(5)]
|
||||
|
||||
* optimize-enums/typeof-kept/input.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(1): ["Bar", "X"]
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
const enum Phase {
|
||||
idle,
|
||||
active,
|
||||
}
|
||||
|
||||
const enum Theme {
|
||||
Light = "Light",
|
||||
Dark = "Dark",
|
||||
}
|
||||
|
||||
// `as` type-cast
|
||||
const a1 = Phase.idle as Phase;
|
||||
const a2 = Phase["active"] as Phase;
|
||||
const a3 = Theme.Light as Theme;
|
||||
|
||||
// `satisfies` type-cast
|
||||
const s1 = Phase.idle satisfies Phase;
|
||||
const s2 = Theme.Dark satisfies Theme;
|
||||
|
||||
// `!` non-null assertion
|
||||
const n1 = Phase.active!;
|
||||
const n2 = Theme.Light!;
|
||||
|
||||
// `<T>` type assertion
|
||||
const t1 = <Phase>Phase.idle;
|
||||
|
||||
// nested / combined
|
||||
const c1 = (Phase.active as Phase) satisfies Phase;
|
||||
const c2 = (Phase.idle satisfies Phase)!;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": [["transform-typescript", { "optimizeConstEnums": true }]]
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// `as` type-cast
|
||||
const a1 = 0;
|
||||
const a2 = 1;
|
||||
const a3 = "Light";
|
||||
// `satisfies` type-cast
|
||||
const s1 = 0;
|
||||
const s2 = "Dark";
|
||||
// `!` non-null assertion
|
||||
const n1 = 1;
|
||||
const n2 = "Light";
|
||||
// `<T>` type assertion
|
||||
const t1 = 0;
|
||||
// nested / combined
|
||||
const c1 = 1;
|
||||
const c2 = 0;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
Direction.Up as Direction;
|
||||
Direction["Down"] as Direction;
|
||||
Direction.Up satisfies Direction;
|
||||
Direction.Down!;
|
||||
<Direction>Direction.Up;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
var Direction = /* @__PURE__ */ function(Direction) {
|
||||
Direction[Direction["Up"] = 0] = "Up";
|
||||
Direction[Direction["Down"] = 1] = "Down";
|
||||
return Direction;
|
||||
}(Direction || {});
|
||||
0;
|
||||
1;
|
||||
0;
|
||||
1;
|
||||
0;
|
||||
Reference in new issue
Block a user