Skip to content

Commit 87cf62c

Browse files
Fix getLexicographicallySmallest
1 parent 0e76a1e commit 87cf62c

File tree

2 files changed

+90
-5
lines changed

2 files changed

+90
-5
lines changed

lib/rules/sort-character-class-elements.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { mention } from "../utils/mention"
1212
import type { ReadonlyFlags } from "regexp-ast-analysis"
1313
import { toUnicodeSet } from "regexp-ast-analysis"
1414
import type { ReadonlyWord } from "refa"
15+
import { getLexicographicallySmallest } from "../utils/lexicographically-smallest"
1516

1617
type CharacterClassElementKind =
1718
| "\\w"
@@ -70,11 +71,7 @@ function getLexicographicallySmallestFromElement(
7071
node.type === "CharacterSet" && node.negate
7172
? toUnicodeSet({ ...node, negate: false }, flags)
7273
: toUnicodeSet(node, flags)
73-
const minimumWords: ReadonlyWord[] = [
74-
...(us.chars.isEmpty ? [] : [[us.chars.ranges[0].min]]),
75-
...us.accept.words,
76-
]
77-
return minimumWords.sort(compareWords).shift() || []
74+
return getLexicographicallySmallest(us) || []
7875
}
7976

8077
/**
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { Word } from "refa"
2+
import type { JS } from "refa"
3+
4+
function findMin<T>(
5+
array: readonly T[],
6+
compare: (a: T, b: T) => number,
7+
): T | undefined {
8+
if (array.length === 0) {
9+
return undefined
10+
}
11+
12+
let min = array[0]
13+
for (let i = 1; i < array.length; i++) {
14+
const item = array[i]
15+
if (compare(item, min) < 0) {
16+
min = item
17+
}
18+
}
19+
return min
20+
}
21+
22+
function compareWords(a: Word, b: Word): number {
23+
const l = Math.min(a.length, b.length)
24+
for (let i = 0; i < l; i++) {
25+
const diff = a[i] - b[i]
26+
if (diff !== 0) {
27+
return diff
28+
}
29+
}
30+
return a.length - b.length
31+
}
32+
33+
/**
34+
* Returns the lexicographically smallest word in the given set or `undefined` if the set is empty.
35+
*/
36+
export function getLexicographicallySmallest(
37+
set: JS.UnicodeSet,
38+
): Word | undefined {
39+
if (set.accept.isEmpty) {
40+
return set.chars.isEmpty ? undefined : [set.chars.ranges[0].min]
41+
}
42+
43+
const words = set.accept.wordSets.map(
44+
(w): Word => w.map((c) => c.ranges[0].min),
45+
)
46+
return findMin(words, compareWords)
47+
}
48+
49+
/**
50+
* Returns the lexicographically smallest word in the given set or `undefined` if the set is empty.
51+
*/
52+
export function getLexicographicallySmallestInConcatenation(
53+
elements: readonly JS.UnicodeSet[],
54+
): Word | undefined {
55+
if (elements.length === 1) {
56+
return getLexicographicallySmallest(elements[0])
57+
}
58+
59+
let smallest: Word = []
60+
for (let i = elements.length - 1; i >= 0; i--) {
61+
const set = elements[i]
62+
if (set.isEmpty) {
63+
return undefined
64+
} else if (set.accept.isEmpty) {
65+
smallest.unshift(set.chars.ranges[0].min)
66+
} else {
67+
let words = set.accept.wordSets.map(
68+
(w): Word => w.map((c) => c.ranges[0].min),
69+
)
70+
// we only have to consider the lexicographically smallest words with unique length
71+
const seenLengths = new Set<number>()
72+
words = words.sort(compareWords).filter((w) => {
73+
if (seenLengths.has(w.length)) {
74+
return false
75+
}
76+
seenLengths.add(w.length)
77+
return true
78+
})
79+
80+
smallest = findMin(
81+
// eslint-disable-next-line no-loop-func -- x
82+
words.map((w): Word => [...w, ...smallest]),
83+
compareWords,
84+
)!
85+
}
86+
}
87+
return smallest
88+
}

0 commit comments

Comments
 (0)