mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
fix(transformer/typescript): enum merging when same name declared in outer scope (#8691)
```typescript
var x = 10;
enum Merge { x = Math.random() }
enum Merge {
y = x // <-- refers to Merge.x
}
```
This case wasn't covered in #8543 and by the [babel test
case](e568916ef3/packages/babel-plugin-transform-typescript/test/fixtures/enum/non-constant-member-reference/input.ts).
To handle it we still have to go through the scope ancestors.
---------
Co-authored-by: Dunqing <dengqing0821@gmail.com>
This commit is contained in:
parent
77ef61a68e
commit
3e509e1c9b
4 changed files with 139 additions and 9 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
use oxc_allocator::Vec as ArenaVec;
|
use oxc_allocator::Vec as ArenaVec;
|
||||||
use oxc_ast::{ast::*, visit::walk_mut, VisitMut, NONE};
|
use oxc_ast::{ast::*, visit::walk_mut, VisitMut, NONE};
|
||||||
|
use oxc_data_structures::stack::NonEmptyStack;
|
||||||
use oxc_ecmascript::ToInt32;
|
use oxc_ecmascript::ToInt32;
|
||||||
use oxc_semantic::ScopeId;
|
use oxc_semantic::{ScopeFlags, ScopeId};
|
||||||
use oxc_span::{Atom, Span, SPAN};
|
use oxc_span::{Atom, Span, SPAN};
|
||||||
use oxc_syntax::{
|
use oxc_syntax::{
|
||||||
number::{NumberBase, ToJsString},
|
number::{NumberBase, ToJsString},
|
||||||
|
|
@ -521,9 +523,9 @@ impl<'a> TypeScriptEnum<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
struct IdentifierReferenceRename<'a, 'ctx> {
|
struct IdentifierReferenceRename<'a, 'ctx> {
|
||||||
enum_name: Atom<'a>,
|
enum_name: Atom<'a>,
|
||||||
enum_scope_id: ScopeId,
|
|
||||||
ctx: &'ctx TraverseCtx<'a>,
|
|
||||||
previous_enum_members: PrevMembers<'a>,
|
previous_enum_members: PrevMembers<'a>,
|
||||||
|
scope_stack: NonEmptyStack<ScopeId>,
|
||||||
|
ctx: &'ctx TraverseCtx<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'ctx> IdentifierReferenceRename<'a, 'ctx> {
|
impl<'a, 'ctx> IdentifierReferenceRename<'a, 'ctx> {
|
||||||
|
|
@ -533,23 +535,70 @@ impl<'a, 'ctx> IdentifierReferenceRename<'a, 'ctx> {
|
||||||
previous_enum_members: PrevMembers<'a>,
|
previous_enum_members: PrevMembers<'a>,
|
||||||
ctx: &'ctx TraverseCtx<'a>,
|
ctx: &'ctx TraverseCtx<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
IdentifierReferenceRename { enum_name, enum_scope_id, ctx, previous_enum_members }
|
IdentifierReferenceRename {
|
||||||
|
enum_name,
|
||||||
|
previous_enum_members,
|
||||||
|
scope_stack: NonEmptyStack::new(enum_scope_id),
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdentifierReferenceRename<'_, '_> {
|
impl IdentifierReferenceRename<'_, '_> {
|
||||||
fn should_reference_enum_member(&self, ident: &IdentifierReference<'_>) -> bool {
|
fn should_reference_enum_member(&self, ident: &IdentifierReference<'_>) -> bool {
|
||||||
|
// Don't need to rename the identifier if it's not a member of the enum,
|
||||||
|
if !self.previous_enum_members.contains_key(&ident.name) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
let symbol_table = self.ctx.scoping.symbols();
|
let symbol_table = self.ctx.scoping.symbols();
|
||||||
let Some(symbol_id) = symbol_table.get_reference(ident.reference_id()).symbol_id() else {
|
let Some(symbol_id) = symbol_table.get_reference(ident.reference_id()).symbol_id() else {
|
||||||
// No symbol found. If the name is found in previous_enum_members,
|
// No symbol found, yet the name is found in previous_enum_members.
|
||||||
// it must be referencing a member declared in a previous enum block: `enum Foo { A }; enum Foo { B = A }`
|
// It must be referencing a member declared in a previous enum block: `enum Foo { A }; enum Foo { B = A }`
|
||||||
return self.previous_enum_members.contains_key(&ident.name);
|
return true;
|
||||||
};
|
};
|
||||||
symbol_table.get_scope_id(symbol_id) == self.enum_scope_id
|
|
||||||
|
let symbol_scope_id = symbol_table.get_scope_id(symbol_id);
|
||||||
|
// Don't need to rename the identifier when it references a nested enum member:
|
||||||
|
//
|
||||||
|
// ```ts
|
||||||
|
// enum OuterEnum {
|
||||||
|
// A = 0,
|
||||||
|
// B = () => {
|
||||||
|
// enum InnerEnum {
|
||||||
|
// A = 0,
|
||||||
|
// B = A,
|
||||||
|
// ^ This references to `InnerEnum.A` should not be renamed
|
||||||
|
// }
|
||||||
|
// return InnerEnum.B;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// `NonEmptyStack` guarantees that the stack is not empty.
|
||||||
|
*self.scope_stack.first().unwrap() == symbol_scope_id
|
||||||
|
// The resolved symbol is declared outside the enum,
|
||||||
|
// and we have checked that the name exists in previous_enum_members:
|
||||||
|
//
|
||||||
|
// ```ts
|
||||||
|
// const A = 0;
|
||||||
|
// enum Foo { A }
|
||||||
|
// enum Foo { B = A }
|
||||||
|
// ^ This should be renamed to Foo.A
|
||||||
|
// ```
|
||||||
|
|| !self.scope_stack.contains(&symbol_scope_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VisitMut<'a> for IdentifierReferenceRename<'a, '_> {
|
impl<'a> VisitMut<'a> for IdentifierReferenceRename<'a, '_> {
|
||||||
|
fn enter_scope(&mut self, _flags: ScopeFlags, scope_id: &Cell<Option<ScopeId>>) {
|
||||||
|
self.scope_stack.push(scope_id.get().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave_scope(&mut self) {
|
||||||
|
self.scope_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
||||||
match expr {
|
match expr {
|
||||||
Expression::Identifier(ident) if self.should_reference_enum_member(ident) => {
|
Expression::Identifier(ident) if self.should_reference_enum_member(ident) => {
|
||||||
|
|
|
||||||
|
|
@ -107,18 +107,65 @@ rebuilt : ScopeId(0): []
|
||||||
|
|
||||||
* enum-member-reference/input.ts
|
* enum-member-reference/input.ts
|
||||||
Missing ReferenceId: "Foo"
|
Missing ReferenceId: "Foo"
|
||||||
|
Missing ReferenceId: "Merge"
|
||||||
|
Missing ReferenceId: "NestInner"
|
||||||
Bindings mismatch:
|
Bindings mismatch:
|
||||||
after transform: ScopeId(1): ["Foo", "a", "b", "c"]
|
after transform: ScopeId(1): ["Foo", "a", "b", "c"]
|
||||||
rebuilt : ScopeId(1): ["Foo"]
|
rebuilt : ScopeId(1): ["Foo"]
|
||||||
Scope flags mismatch:
|
Scope flags mismatch:
|
||||||
after transform: ScopeId(1): ScopeFlags(0x0)
|
after transform: ScopeId(1): ScopeFlags(0x0)
|
||||||
rebuilt : ScopeId(1): ScopeFlags(Function)
|
rebuilt : ScopeId(1): ScopeFlags(Function)
|
||||||
|
Bindings mismatch:
|
||||||
|
after transform: ScopeId(2): ["Merge", "x"]
|
||||||
|
rebuilt : ScopeId(2): ["Merge"]
|
||||||
|
Scope flags mismatch:
|
||||||
|
after transform: ScopeId(2): ScopeFlags(0x0)
|
||||||
|
rebuilt : ScopeId(2): ScopeFlags(Function)
|
||||||
|
Bindings mismatch:
|
||||||
|
after transform: ScopeId(3): ["Merge", "y"]
|
||||||
|
rebuilt : ScopeId(3): ["Merge"]
|
||||||
|
Scope flags mismatch:
|
||||||
|
after transform: ScopeId(3): ScopeFlags(0x0)
|
||||||
|
rebuilt : ScopeId(3): ScopeFlags(Function)
|
||||||
|
Bindings mismatch:
|
||||||
|
after transform: ScopeId(4): ["NestOuter", "a", "b"]
|
||||||
|
rebuilt : ScopeId(4): ["NestOuter"]
|
||||||
|
Scope flags mismatch:
|
||||||
|
after transform: ScopeId(4): ScopeFlags(0x0)
|
||||||
|
rebuilt : ScopeId(4): ScopeFlags(Function)
|
||||||
|
Bindings mismatch:
|
||||||
|
after transform: ScopeId(6): ["NestInner", "a", "b"]
|
||||||
|
rebuilt : ScopeId(6): ["NestInner"]
|
||||||
|
Scope flags mismatch:
|
||||||
|
after transform: ScopeId(6): ScopeFlags(0x0)
|
||||||
|
rebuilt : ScopeId(6): ScopeFlags(Function)
|
||||||
|
Symbol reference IDs mismatch for "x":
|
||||||
|
after transform: SymbolId(0): [ReferenceId(2), ReferenceId(4)]
|
||||||
|
rebuilt : SymbolId(0): [ReferenceId(7)]
|
||||||
Symbol flags mismatch for "Foo":
|
Symbol flags mismatch for "Foo":
|
||||||
after transform: SymbolId(1): SymbolFlags(RegularEnum)
|
after transform: SymbolId(1): SymbolFlags(RegularEnum)
|
||||||
rebuilt : SymbolId(1): SymbolFlags(FunctionScopedVariable)
|
rebuilt : SymbolId(1): SymbolFlags(FunctionScopedVariable)
|
||||||
Symbol reference IDs mismatch for "Foo":
|
Symbol reference IDs mismatch for "Foo":
|
||||||
after transform: SymbolId(5): [ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6), ReferenceId(7), ReferenceId(8), ReferenceId(9)]
|
after transform: SymbolId(14): [ReferenceId(8), ReferenceId(9), ReferenceId(10), ReferenceId(11), ReferenceId(12), ReferenceId(13), ReferenceId(14)]
|
||||||
rebuilt : SymbolId(2): [ReferenceId(0), ReferenceId(1), ReferenceId(2), ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6), ReferenceId(8)]
|
rebuilt : SymbolId(2): [ReferenceId(0), ReferenceId(1), ReferenceId(2), ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6), ReferenceId(8)]
|
||||||
|
Symbol flags mismatch for "Merge":
|
||||||
|
after transform: SymbolId(5): SymbolFlags(RegularEnum)
|
||||||
|
rebuilt : SymbolId(3): SymbolFlags(FunctionScopedVariable)
|
||||||
|
Symbol redeclarations mismatch for "Merge":
|
||||||
|
after transform: SymbolId(5): [Span { start: 103, end: 108 }]
|
||||||
|
rebuilt : SymbolId(3): []
|
||||||
|
Symbol reference IDs mismatch for "Merge":
|
||||||
|
after transform: SymbolId(16): [ReferenceId(20), ReferenceId(21), ReferenceId(22)]
|
||||||
|
rebuilt : SymbolId(5): [ReferenceId(16), ReferenceId(17), ReferenceId(18), ReferenceId(19)]
|
||||||
|
Symbol flags mismatch for "NestOuter":
|
||||||
|
after transform: SymbolId(8): SymbolFlags(RegularEnum)
|
||||||
|
rebuilt : SymbolId(6): SymbolFlags(FunctionScopedVariable)
|
||||||
|
Symbol flags mismatch for "NestInner":
|
||||||
|
after transform: SymbolId(11): SymbolFlags(RegularEnum)
|
||||||
|
rebuilt : SymbolId(8): SymbolFlags(BlockScopedVariable)
|
||||||
|
Symbol reference IDs mismatch for "NestInner":
|
||||||
|
after transform: SymbolId(18): [ReferenceId(31), ReferenceId(32), ReferenceId(33), ReferenceId(34), ReferenceId(35)]
|
||||||
|
rebuilt : SymbolId(9): [ReferenceId(25), ReferenceId(26), ReferenceId(28), ReferenceId(29), ReferenceId(30), ReferenceId(31)]
|
||||||
|
|
||||||
* export-elimination/input.ts
|
* export-elimination/input.ts
|
||||||
Bindings mismatch:
|
Bindings mismatch:
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,17 @@ enum Foo {
|
||||||
b = a,
|
b = a,
|
||||||
c = b + x,
|
c = b + x,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Merge { x = Math.random() }
|
||||||
|
enum Merge { y = x }
|
||||||
|
|
||||||
|
enum NestOuter {
|
||||||
|
a,
|
||||||
|
b = (() => {
|
||||||
|
enum NestInner {
|
||||||
|
a = Math.random(),
|
||||||
|
b = a
|
||||||
|
}
|
||||||
|
return NestInner.b;
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,23 @@ var Foo = function(Foo) {
|
||||||
Foo[Foo['c'] = Foo.b + x] = 'c';
|
Foo[Foo['c'] = Foo.b + x] = 'c';
|
||||||
return Foo;
|
return Foo;
|
||||||
}(Foo || {});
|
}(Foo || {});
|
||||||
|
var Merge = function(Merge) {
|
||||||
|
Merge[Merge['x'] = Math.random()] = 'x';
|
||||||
|
return Merge;
|
||||||
|
}(Merge || {});
|
||||||
|
Merge = function(Merge) {
|
||||||
|
Merge[Merge['y'] = Merge.x] = 'y';
|
||||||
|
return Merge;
|
||||||
|
}(Merge || {});
|
||||||
|
var NestOuter = function(NestOuter) {
|
||||||
|
NestOuter[NestOuter['a'] = 0] = 'a';
|
||||||
|
NestOuter[NestOuter['b'] = (() => {
|
||||||
|
let NestInner = function(NestInner) {
|
||||||
|
NestInner[NestInner['a'] = Math.random()] = 'a';
|
||||||
|
NestInner[NestInner['b'] = NestInner.a] = 'b';
|
||||||
|
return NestInner;
|
||||||
|
}({});
|
||||||
|
return NestInner.b;
|
||||||
|
})()] = 'b';
|
||||||
|
return NestOuter;
|
||||||
|
}(NestOuter || {});
|
||||||
Loading…
Reference in a new issue