Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6df0575

Browse filesBrowse files
author
Andy
authored
Remove length limit on spelling suggestions; use levenshteinWithMax for performance (microsoft#19937)
* Remove length limit on spelling suggestions; use levenshteinWithMax for performance * Remove suggestion exceptions * Move to checker.ts * Reintroduce candidateName max length
1 parent 5b30bef commit 6df0575
Copy full SHA for 6df0575

2 files changed

+77-66Lines changed: 77 additions & 66 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+77-43Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15748,61 +15748,95 @@ namespace ts {
1574815748
* except for candidates:
1574915749
* * With no name
1575015750
* * Whose meaning doesn't match the `meaning` parameter.
15751-
* * Whose length differs from the target name by more than 0.3 of the length of the name.
15751+
* * Whose length differs from the target name by more than 0.34 of the length of the name.
1575215752
* * Whose levenshtein distance is more than 0.4 of the length of the name
1575315753
* (0.4 allows 1 substitution/transposition for every 5 characters,
1575415754
* and 1 insertion/deletion at 3 characters)
15755-
* Names longer than 30 characters don't get suggestions because Levenshtein distance is an n**2 algorithm.
1575615755
*/
1575715756
function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined {
15758-
const worstDistance = name.length * 0.4;
15759-
const maximumLengthDifference = Math.min(3, name.length * 0.34);
15760-
let bestDistance = Number.MAX_VALUE;
15761-
let bestCandidate = undefined;
15757+
const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34));
15758+
let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result isn't better than this, don't bother.
15759+
let bestCandidate: Symbol | undefined;
1576215760
let justCheckExactMatches = false;
15763-
if (name.length > 30) {
15764-
return undefined;
15765-
}
15766-
name = name.toLowerCase();
15761+
const nameLowerCase = name.toLowerCase();
1576715762
for (const candidate of symbols) {
15768-
let candidateName = symbolName(candidate);
15769-
if (candidate.flags & meaning &&
15770-
candidateName &&
15771-
Math.abs(candidateName.length - name.length) < maximumLengthDifference) {
15772-
candidateName = candidateName.toLowerCase();
15773-
if (candidateName === name) {
15774-
return candidate;
15775-
}
15776-
if (justCheckExactMatches) {
15777-
continue;
15778-
}
15779-
if (candidateName.length < 3 ||
15780-
name.length < 3 ||
15781-
candidateName === "eval" ||
15782-
candidateName === "intl" ||
15783-
candidateName === "undefined" ||
15784-
candidateName === "map" ||
15785-
candidateName === "nan" ||
15786-
candidateName === "set") {
15787-
continue;
15788-
}
15789-
const distance = levenshtein(name, candidateName);
15790-
if (distance > worstDistance) {
15791-
continue;
15792-
}
15793-
if (distance < 3) {
15794-
justCheckExactMatches = true;
15795-
bestCandidate = candidate;
15796-
}
15797-
else if (distance < bestDistance) {
15798-
bestDistance = distance;
15799-
bestCandidate = candidate;
15800-
}
15763+
const candidateName = symbolName(candidate);
15764+
if (!(candidate.flags & meaning && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference)) {
15765+
continue;
15766+
}
15767+
const candidateNameLowerCase = candidateName.toLowerCase();
15768+
if (candidateNameLowerCase === nameLowerCase) {
15769+
return candidate;
15770+
}
15771+
if (justCheckExactMatches) {
15772+
continue;
15773+
}
15774+
if (candidateName.length < 3) {
15775+
// Don't bother, user would have noticed a 2-character name having an extra character
15776+
continue;
15777+
}
15778+
// Only care about a result better than the best so far.
15779+
const distance = levenshteinWithMax(nameLowerCase, candidateNameLowerCase, bestDistance - 1);
15780+
if (distance === undefined) {
15781+
continue;
15782+
}
15783+
if (distance < 3) {
15784+
justCheckExactMatches = true;
15785+
bestCandidate = candidate;
15786+
}
15787+
else {
15788+
Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined
15789+
bestDistance = distance;
15790+
bestCandidate = candidate;
1580115791
}
1580215792
}
1580315793
return bestCandidate;
1580415794
}
1580515795

15796+
function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined {
15797+
let previous = new Array(s2.length + 1);
15798+
let current = new Array(s2.length + 1);
15799+
/** Represents any value > max. We don't care about the particular value. */
15800+
const big = max + 1;
15801+
15802+
for (let i = 0; i <= s2.length; i++) {
15803+
previous[i] = i;
15804+
}
15805+
15806+
for (let i = 1; i <= s1.length; i++) {
15807+
const c1 = s1.charCodeAt(i - 1);
15808+
const minJ = i > max ? i - max : 1;
15809+
const maxJ = s2.length > max + i ? max + i : s2.length;
15810+
current[0] = i;
15811+
/** Smallest value of the matrix in the ith column. */
15812+
let colMin = i;
15813+
for (let j = 1; j < minJ; j++) {
15814+
current[j] = big;
15815+
}
15816+
for (let j = minJ; j <= maxJ; j++) {
15817+
const dist = c1 === s2.charCodeAt(j - 1)
15818+
? previous[j - 1]
15819+
: Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ previous[j - 1] + 2);
15820+
current[j] = dist;
15821+
colMin = Math.min(colMin, dist);
15822+
}
15823+
for (let j = maxJ + 1; j <= s2.length; j++) {
15824+
current[j] = big;
15825+
}
15826+
if (colMin > max) {
15827+
// Give up -- everything in this column is > max and it can't get better in future columns.
15828+
return undefined;
15829+
}
15830+
15831+
const temp = previous;
15832+
previous = current;
15833+
current = temp;
15834+
}
15835+
15836+
const res = previous[s2.length];
15837+
return res > max ? undefined : res;
15838+
}
15839+
1580615840
function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) {
1580715841
if (prop &&
1580815842
noUnusedIdentifiers &&
Collapse file

‎src/compiler/utilities.ts‎

Copy file name to clipboardExpand all lines: src/compiler/utilities.ts
-23Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3518,29 +3518,6 @@ namespace ts {
35183518
return 0;
35193519
}
35203520

3521-
export function levenshtein(s1: string, s2: string): number {
3522-
let previous: number[] = new Array(s2.length + 1);
3523-
let current: number[] = new Array(s2.length + 1);
3524-
for (let i = 0; i < s2.length + 1; i++) {
3525-
previous[i] = i;
3526-
current[i] = -1;
3527-
}
3528-
for (let i = 1; i < s1.length + 1; i++) {
3529-
current[0] = i;
3530-
for (let j = 1; j < s2.length + 1; j++) {
3531-
current[j] = Math.min(
3532-
previous[j] + 1,
3533-
current[j - 1] + 1,
3534-
previous[j - 1] + (s1[i - 1] === s2[j - 1] ? 0 : 2));
3535-
}
3536-
// shift current back to previous, and then reuse previous' array
3537-
const tmp = previous;
3538-
previous = current;
3539-
current = tmp;
3540-
}
3541-
return previous[previous.length - 1];
3542-
}
3543-
35443521
export function skipAlias(symbol: Symbol, checker: TypeChecker) {
35453522
return symbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
35463523
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.