Skip to content

Commit 57adbb2

Browse files
Fix isEqualNodes (#557)
* Fix `isEqualNodes` * Fixed isCovered
1 parent bc56ef0 commit 57adbb2

File tree

3 files changed

+160
-163
lines changed

3 files changed

+160
-163
lines changed

lib/utils/regexp-ast/is-covered.ts

Lines changed: 96 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import type {
88
LookaroundAssertion,
99
} from "@eslint-community/regexpp/ast"
1010
import { isEqualNodes } from "./is-equals"
11-
import type { ReadonlyFlags, ToCharSetElement } from "regexp-ast-analysis"
12-
import { toCharSet } from "regexp-ast-analysis"
11+
import type {
12+
ReadonlyFlags,
13+
ToCharSetElement,
14+
ToUnicodeSetElement,
15+
} from "regexp-ast-analysis"
16+
import { toCharSet, toUnicodeSet } from "regexp-ast-analysis"
1317
import type { CharSet } from "refa"
1418

1519
type Options = {
@@ -54,6 +58,10 @@ class NormalizedCharacter implements NormalizedNodeBase {
5458
return new NormalizedCharacter(toCharSet(element, options.flags))
5559
}
5660

61+
public static fromChars(charSet: CharSet) {
62+
return new NormalizedCharacter(charSet)
63+
}
64+
5765
private constructor(charSet: CharSet) {
5866
this.charSet = charSet
5967
}
@@ -69,7 +77,7 @@ class NormalizedAlternative implements NormalizedNodeBase {
6977

7078
public readonly raw: string
7179

72-
public readonly elements: NormalizedNode[]
80+
public readonly elements: readonly NormalizedNode[]
7381

7482
public static fromAlternative(node: Alternative, options: Options) {
7583
const normalizeElements = [
@@ -107,7 +115,7 @@ class NormalizedAlternative implements NormalizedNodeBase {
107115

108116
public static fromElements(
109117
elements: NormalizedNode[],
110-
node: Alternative | Quantifier,
118+
node: Alternative | Quantifier | ToUnicodeSetElement,
111119
) {
112120
const normalizeElements = [
113121
...NormalizedAlternative.normalizedElements(function* () {
@@ -131,7 +139,7 @@ class NormalizedAlternative implements NormalizedNodeBase {
131139

132140
private constructor(
133141
elements: NormalizedNode[],
134-
node: Alternative | Quantifier,
142+
node: Alternative | Quantifier | ToUnicodeSetElement,
135143
) {
136144
this.raw = node.raw
137145
this.elements = elements
@@ -148,11 +156,9 @@ class NormalizedDisjunctions implements NormalizedNodeBase {
148156

149157
public readonly raw: string
150158

151-
public readonly node: CapturingGroup | Group | Pattern
159+
private readonly getAlternatives: () => readonly NormalizedAlternative[]
152160

153-
private readonly options: Options
154-
155-
public normalizedAlternatives?: NormalizedAlternative[]
161+
private normalizedAlternatives?: readonly NormalizedAlternative[]
156162

157163
public static fromNode(
158164
node: CapturingGroup | Group | Pattern,
@@ -164,32 +170,35 @@ class NormalizedDisjunctions implements NormalizedNodeBase {
164170
options,
165171
)
166172
}
167-
return new NormalizedDisjunctions(node, options)
173+
return new NormalizedDisjunctions(node, () => {
174+
return node.alternatives.map((alt) => {
175+
const n = normalizeNode(alt, options)
176+
if (n.type === "NormalizedAlternative") {
177+
return n
178+
}
179+
return NormalizedAlternative.fromElements([n], alt)
180+
})
181+
})
182+
}
183+
184+
public static fromAlternatives(
185+
alternatives: readonly NormalizedAlternative[],
186+
node: CapturingGroup | Group | Pattern | ToUnicodeSetElement,
187+
) {
188+
return new NormalizedDisjunctions(node, () => alternatives)
168189
}
169190

170191
private constructor(
171-
node: CapturingGroup | Group | Pattern,
172-
options: Options,
192+
node: CapturingGroup | Group | Pattern | ToUnicodeSetElement,
193+
getAlternatives: () => readonly NormalizedAlternative[],
173194
) {
174195
this.raw = node.raw
175-
this.node = node
176-
this.options = options
196+
this.getAlternatives = getAlternatives
177197
}
178198

179-
public get alternatives() {
180-
if (this.normalizedAlternatives) {
181-
return this.normalizedAlternatives
182-
}
183-
this.normalizedAlternatives = []
184-
for (const alt of this.node.alternatives) {
185-
const node = normalizeNode(alt, this.options)
186-
if (node.type === "NormalizedAlternative") {
187-
this.normalizedAlternatives.push(node)
188-
} else {
189-
this.normalizedAlternatives.push(
190-
NormalizedAlternative.fromElements([node], alt),
191-
)
192-
}
199+
public get alternatives(): readonly NormalizedAlternative[] {
200+
if (!this.normalizedAlternatives) {
201+
this.normalizedAlternatives = this.getAlternatives()
193202
}
194203
return this.normalizedAlternatives
195204
}
@@ -208,7 +217,7 @@ class NormalizedLookaroundAssertion implements NormalizedNodeBase {
208217

209218
private readonly options: Options
210219

211-
public normalizedAlternatives?: NormalizedAlternative[]
220+
private normalizedAlternatives?: NormalizedAlternative[]
212221

213222
public static fromNode(node: LookaroundAssertion, options: Options) {
214223
return new NormalizedLookaroundAssertion(node, options)
@@ -220,7 +229,7 @@ class NormalizedLookaroundAssertion implements NormalizedNodeBase {
220229
this.options = options
221230
}
222231

223-
public get alternatives() {
232+
public get alternatives(): readonly NormalizedAlternative[] {
224233
if (this.normalizedAlternatives) {
225234
return this.normalizedAlternatives
226235
}
@@ -425,44 +434,66 @@ function normalizeNodeWithoutCache(
425434
node: Node,
426435
options: Options,
427436
): NormalizedNode {
428-
if (
429-
node.type === "CharacterSet" ||
430-
node.type === "CharacterClass" ||
431-
node.type === "Character" ||
432-
node.type === "CharacterClassRange"
433-
) {
434-
// FIXME: TS Error
435-
// @ts-expect-error -- FIXME
436-
return NormalizedCharacter.fromElement(node, options)
437-
}
438-
if (node.type === "Alternative") {
439-
return NormalizedAlternative.fromAlternative(node, options)
440-
}
441-
if (node.type === "Quantifier") {
442-
return NormalizedOptional.fromQuantifier(node, options)
443-
}
444-
if (
445-
node.type === "CapturingGroup" ||
446-
node.type === "Group" ||
447-
node.type === "Pattern"
448-
) {
449-
return NormalizedDisjunctions.fromNode(node, options)
450-
}
451-
if (node.type === "RegExpLiteral") {
452-
return normalizeNode(node.pattern, options)
453-
}
454-
if (node.type === "Assertion") {
455-
if (node.kind === "lookahead" || node.kind === "lookbehind") {
456-
return NormalizedLookaroundAssertion.fromNode(node, options)
437+
switch (node.type) {
438+
case "CharacterSet":
439+
case "CharacterClass":
440+
case "Character":
441+
case "CharacterClassRange":
442+
case "ExpressionCharacterClass":
443+
case "ClassIntersection":
444+
case "ClassSubtraction":
445+
case "ClassStringDisjunction":
446+
case "StringAlternative": {
447+
const set = toUnicodeSet(node, options.flags)
448+
if (set.accept.isEmpty) {
449+
return NormalizedCharacter.fromChars(set.chars)
450+
}
451+
452+
const alternatives = set.wordSets.map((wordSet) => {
453+
return NormalizedAlternative.fromElements(
454+
wordSet.map(NormalizedCharacter.fromChars),
455+
node,
456+
)
457+
})
458+
return NormalizedDisjunctions.fromAlternatives(alternatives, node)
457459
}
458-
return NormalizedOther.fromNode(node)
460+
461+
case "Alternative":
462+
return NormalizedAlternative.fromAlternative(node, options)
463+
464+
case "Quantifier":
465+
return NormalizedOptional.fromQuantifier(node, options)
466+
467+
case "CapturingGroup":
468+
case "Group":
469+
case "Pattern":
470+
return NormalizedDisjunctions.fromNode(node, options)
471+
472+
case "Assertion":
473+
if (node.kind === "lookahead" || node.kind === "lookbehind") {
474+
return NormalizedLookaroundAssertion.fromNode(node, options)
475+
}
476+
return NormalizedOther.fromNode(node)
477+
478+
case "RegExpLiteral":
479+
return normalizeNode(node.pattern, options)
480+
481+
case "Backreference":
482+
case "Flags":
483+
return NormalizedOther.fromNode(node)
484+
485+
default:
486+
return assertNever(node)
459487
}
460-
return NormalizedOther.fromNode(node)
488+
}
489+
490+
function assertNever(value: never): never {
491+
throw new Error(`Invalid value: ${value}`)
461492
}
462493

463494
/** Check whether the right node is covered by the left nodes. */
464495
function isCoveredAnyNode(
465-
left: NormalizedNode[],
496+
left: readonly NormalizedNode[],
466497
right: NormalizedNode,
467498
options: Options,
468499
) {
@@ -476,8 +507,8 @@ function isCoveredAnyNode(
476507

477508
/** Check whether the right nodes is covered by the left nodes. */
478509
function isCoveredAltNodes(
479-
leftNodes: NormalizedNode[],
480-
rightNodes: NormalizedNode[],
510+
leftNodes: readonly NormalizedNode[],
511+
rightNodes: readonly NormalizedNode[],
481512
options: Options,
482513
): boolean {
483514
const left = options.canOmitRight ? omitEnds(leftNodes) : [...leftNodes]
@@ -561,7 +592,7 @@ function isCoveredAltNodes(
561592
/**
562593
* Exclude the end optionals.
563594
*/
564-
function omitEnds(nodes: NormalizedNode[]): NormalizedNode[] {
595+
function omitEnds(nodes: readonly NormalizedNode[]): NormalizedNode[] {
565596
for (let index = nodes.length - 1; index >= 0; index--) {
566597
const node = nodes[index]
567598
if (node.type !== "NormalizedOptional") {

0 commit comments

Comments
 (0)