Skip to content

Commit 21e544d

Browse files
committed
suggestionList: Add early exit for distances about threshold
1 parent 122b741 commit 21e544d

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

src/jsutils/__tests__/suggestionList-test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ describe('suggestionList', () => {
2323
expectSuggestions('green', ['greenish']).to.deep.equal(['greenish']);
2424
});
2525

26+
it('Rejects options with distance that exceeds threshold', () => {
27+
expectSuggestions('aaaa', ['aaab']).to.deep.equal(['aaab']);
28+
expectSuggestions('aaaa', ['aabb']).to.deep.equal(['aabb']);
29+
expectSuggestions('aaaa', ['abbb']).to.deep.equal([]);
30+
31+
expectSuggestions('ab', ['ca']).to.deep.equal([]);
32+
});
33+
2634
it('Returns options with different case', () => {
2735
// cSpell:ignore verylongstring
2836
expectSuggestions('verylongstring', ['VERYLONGSTRING']).to.deep.equal([

src/jsutils/suggestionList.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ export default function suggestionList(
1313

1414
const inputThreshold = input.length / 2;
1515
for (const option of options) {
16-
const distance = lexicalDistance.measure(option);
1716
const threshold = Math.max(inputThreshold, option.length / 2, 1);
18-
if (distance <= threshold) {
17+
const distance = lexicalDistance.measure(option, threshold);
18+
if (distance !== undefined) {
1919
optionsByDistance[option] = distance;
2020
}
2121
}
22+
2223
return Object.keys(optionsByDistance).sort((a, b) => {
2324
const distanceDiff = optionsByDistance[a] - optionsByDistance[b];
2425
return distanceDiff !== 0 ? distanceDiff : a.localeCompare(b);
@@ -55,7 +56,7 @@ class LexicalDistance {
5556
];
5657
}
5758

58-
measure(option: string): number {
59+
measure(option: string, threshold: number): number | void {
5960
if (this._input === option) {
6061
return 0;
6162
}
@@ -69,9 +70,14 @@ class LexicalDistance {
6970

7071
const a = optionLowerCase;
7172
const b = this._inputLowerCase;
73+
7274
const aLength = a.length;
7375
const bLength = b.length;
7476

77+
if (Math.abs(aLength - bLength) > threshold) {
78+
return undefined;
79+
}
80+
7581
const rows = this._rows;
7682
for (let j = 0; j <= bLength; j++) {
7783
rows[0][j] = j;
@@ -81,7 +87,7 @@ class LexicalDistance {
8187
const upRow = rows[(i - 1) % 3];
8288
const currentRow = rows[i % 3];
8389

84-
currentRow[0] = i;
90+
let smallestCell = (currentRow[0] = i);
8591
for (let j = 1; j <= bLength; j++) {
8692
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
8793

@@ -97,10 +103,20 @@ class LexicalDistance {
97103
currentCell = Math.min(currentCell, doubleDiagonalCell + 1);
98104
}
99105

106+
if (currentCell < smallestCell) {
107+
smallestCell = currentCell;
108+
}
109+
100110
currentRow[j] = currentCell;
101111
}
112+
113+
// Early exit, since distance can't go smaller than smallest element of the previous row.
114+
if (smallestCell > threshold) {
115+
return undefined;
116+
}
102117
}
103118

104-
return rows[aLength % 3][bLength];
119+
const distance = rows[aLength % 3][bLength];
120+
return distance <= threshold ? distance : undefined;
105121
}
106122
}

0 commit comments

Comments
 (0)