@@ -16,11 +16,7 @@ export interface GroupSpecifiers {
16
16
* Called when visiting the Alternative.
17
17
* For ES2025, manage nesting with new Alternative scopes.
18
18
*/
19
- enterAlternative : ( ) => void
20
- /**
21
- * Called when leaving the Alternative.
22
- */
23
- leaveAlternative : ( ) => void
19
+ enterAlternative : ( index : number ) => void
24
20
/**
25
21
* Called when leaving the Disjunction.
26
22
*/
@@ -40,7 +36,7 @@ export interface GroupSpecifiers {
40
36
}
41
37
42
38
export class GroupSpecifiersAsES2018 implements GroupSpecifiers {
43
- private groupName = new Set < string > ( )
39
+ private readonly groupName = new Set < string > ( )
44
40
45
41
public clear ( ) : void {
46
42
this . groupName . clear ( )
@@ -72,77 +68,100 @@ export class GroupSpecifiersAsES2018 implements GroupSpecifiers {
72
68
// Prior to ES2025, it does not manage alternative scopes.
73
69
}
74
70
75
- // eslint-disable-next-line class-methods-use-this
76
- public leaveAlternative ( ) : void {
77
- // Prior to ES2025, it does not manage alternative scopes.
78
- }
79
-
80
71
// eslint-disable-next-line class-methods-use-this
81
72
public leaveDisjunction ( ) : void {
82
73
// Prior to ES2025, it does not manage disjunction scopes.
83
74
}
84
75
}
85
76
86
- export class GroupSpecifiersAsES2025 implements GroupSpecifiers {
87
- private groupNamesAddedInDisjunction = new Set < string > ( )
88
- private groupNamesAddedInUpperDisjunctionStack : Set < string > [ ] = [ ]
89
- private groupNamesInScope = new Set < string > ( )
90
- private groupNamesInUpperScopeStack : Set < string > [ ] = [ ]
77
+ /**
78
+ * Track disjunction structure to determine whether a duplicate
79
+ * capture group name is allowed because it is in a separate branch.
80
+ */
81
+ class BranchID {
82
+ public readonly parent : BranchID | null
83
+ private readonly base : BranchID
84
+ public constructor ( parent : BranchID | null , base : BranchID | null ) {
85
+ // Parent disjunction branch
86
+ this . parent = parent
87
+ // Identifies this set of sibling branches
88
+ this . base = base ?? this
89
+ }
90
+
91
+ /**
92
+ * A branch is separate from another branch if they or any of
93
+ * their parents are siblings in a given disjunction
94
+ */
95
+ public separatedFrom ( other : BranchID ) : boolean {
96
+ if ( this . base === other . base && this !== other ) {
97
+ return true
98
+ }
99
+ if ( other . parent && this . separatedFrom ( other . parent ) ) {
100
+ return true
101
+ }
102
+ return this . parent ?. separatedFrom ( other ) ?? false
103
+ }
104
+
105
+ public child ( ) {
106
+ return new BranchID ( this , null )
107
+ }
108
+
109
+ public sibling ( ) {
110
+ return new BranchID ( this . parent , this . base )
111
+ }
112
+ }
91
113
92
- private groupNamesInPattern = new Set < string > ( )
114
+ export class GroupSpecifiersAsES2025 implements GroupSpecifiers {
115
+ private branchID = new BranchID ( null , null )
116
+ private readonly groupNames = new Map < string , BranchID [ ] > ( )
93
117
94
118
public clear ( ) : void {
95
- this . groupNamesAddedInDisjunction . clear ( )
96
- this . groupNamesAddedInUpperDisjunctionStack . length = 0
97
- this . groupNamesInScope . clear ( )
98
- this . groupNamesInUpperScopeStack . length = 0
99
- this . groupNamesInPattern . clear ( )
119
+ this . branchID = new BranchID ( null , null )
120
+ this . groupNames . clear ( )
100
121
}
101
122
102
123
public isEmpty ( ) : boolean {
103
- return ! this . groupNamesInPattern . size
124
+ return ! this . groupNames . size
104
125
}
105
126
106
127
public enterDisjunction ( ) : void {
107
- this . groupNamesAddedInUpperDisjunctionStack . push (
108
- this . groupNamesAddedInDisjunction ,
109
- )
110
- // Clear groupNamesAddedInDisjunction to store the groupName added in this Disjunction.
111
- this . groupNamesAddedInDisjunction = new Set ( )
128
+ this . branchID = this . branchID . child ( )
112
129
}
113
130
114
- public enterAlternative ( ) : void {
115
- this . groupNamesInUpperScopeStack . push ( this . groupNamesInScope )
116
- this . groupNamesInScope = new Set ( this . groupNamesInScope )
117
- }
118
-
119
- public leaveAlternative ( ) : void {
120
- this . groupNamesInScope = this . groupNamesInUpperScopeStack . pop ( ) !
131
+ public enterAlternative ( index : number ) : void {
132
+ if ( index === 0 ) {
133
+ return
134
+ }
135
+ this . branchID = this . branchID . sibling ( )
121
136
}
122
137
123
138
public leaveDisjunction ( ) : void {
124
- const groupNamesAddedInDisjunction = this . groupNamesAddedInDisjunction
125
- this . groupNamesAddedInDisjunction =
126
- this . groupNamesAddedInUpperDisjunctionStack . pop ( ) !
127
- for ( const groupName of groupNamesAddedInDisjunction ) {
128
- // Adds the groupName added in Disjunction to groupNamesInScope.
129
- this . groupNamesInScope . add ( groupName )
130
- // Adds the groupName added in Disjunction to the upper Disjunction.
131
- this . groupNamesAddedInDisjunction . add ( groupName )
132
- }
139
+ this . branchID = this . branchID . parent !
133
140
}
134
141
135
142
public hasInPattern ( name : string ) : boolean {
136
- return this . groupNamesInPattern . has ( name )
143
+ return this . groupNames . has ( name )
137
144
}
138
145
139
146
public hasInScope ( name : string ) : boolean {
140
- return this . groupNamesInScope . has ( name )
147
+ const branches = this . groupNames . get ( name )
148
+ if ( ! branches ) {
149
+ return false
150
+ }
151
+ for ( const branch of branches ) {
152
+ if ( ! branch . separatedFrom ( this . branchID ) ) {
153
+ return true
154
+ }
155
+ }
156
+ return false
141
157
}
142
158
143
159
public addToScope ( name : string ) : void {
144
- this . groupNamesInScope . add ( name )
145
- this . groupNamesAddedInDisjunction . add ( name )
146
- this . groupNamesInPattern . add ( name )
160
+ const branches = this . groupNames . get ( name )
161
+ if ( branches ) {
162
+ branches . push ( this . branchID )
163
+ return
164
+ }
165
+ this . groupNames . set ( name , [ this . branchID ] )
147
166
}
148
167
}
0 commit comments