Skip to content

Commit 422fd19

Browse files
ZzzenDanielRosenwassertypescript-botsandersn
authored
Spelling correction fixes should not be case-agnostic for two equally weighed options (#39060)
* Spelling correction fixes should not be case-agnostic when two equally weighed options occur. fixes #17219 * update tests * Update src/compiler/core.ts Co-authored-by: Daniel Rosenwasser <[email protected]> * init bestCaseSensitiveDistance lazily * Add a test case with a class named the same as an instance, except for case * make the core levenshtein distance check case-aware * Update package-lock.json * use fractional Levenshtein distance * fix weight of Levenshtein distance * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update package-lock.json * Update src/compiler/core.ts Co-authored-by: Nathan Shively-Sanders <[email protected]> * refactor * Update package-lock.json * revert unnecessary changes * Update package-lock.json * increase bestDistance * increase bestDistance again * make changes minimal * Update package-lock.json * Update package-lock.json * Update package-lock.json Co-authored-by: Daniel Rosenwasser <[email protected]> Co-authored-by: TypeScript Bot <[email protected]> Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 4a9e2be commit 422fd19

8 files changed

+69
-33
lines changed

src/compiler/core.ts

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,11 +1927,9 @@ namespace ts {
19271927

19281928
/**
19291929
* Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
1930-
* Names less than length 3 only check for case-insensitive equality, not Levenshtein distance.
1930+
* Names less than length 3 only check for case-insensitive equality.
19311931
*
1932-
* If there is a candidate that's the same except for case, return that.
1933-
* If there is a candidate that's within one edit of the name, return that.
1934-
* Otherwise, return the candidate with the smallest Levenshtein distance,
1932+
* find the candidate with the smallest Levenshtein distance,
19351933
* except for candidates:
19361934
* * With no name
19371935
* * Whose length differs from the target name by more than 0.34 of the length of the name.
@@ -1941,41 +1939,28 @@ namespace ts {
19411939
*/
19421940
export function getSpellingSuggestion<T>(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined {
19431941
const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34));
1944-
let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result isn't better than this, don't bother.
1942+
let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother.
19451943
let bestCandidate: T | undefined;
1946-
let justCheckExactMatches = false;
1947-
const nameLowerCase = name.toLowerCase();
19481944
for (const candidate of candidates) {
19491945
const candidateName = getName(candidate);
1950-
if (candidateName !== undefined && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference) {
1951-
const candidateNameLowerCase = candidateName.toLowerCase();
1952-
if (candidateNameLowerCase === nameLowerCase) {
1953-
if (candidateName === name) {
1954-
continue;
1955-
}
1956-
return candidate;
1957-
}
1958-
if (justCheckExactMatches) {
1946+
if (candidateName !== undefined && Math.abs(candidateName.length - name.length) <= maximumLengthDifference) {
1947+
if (candidateName === name) {
19591948
continue;
19601949
}
1961-
if (candidateName.length < 3) {
1962-
// Don't bother, user would have noticed a 2-character name having an extra character
1950+
// Only consider candidates less than 3 characters long when they differ by case.
1951+
// Otherwise, don't bother, since a user would usually notice differences of a 2-character name.
1952+
if (candidateName.length < 3 && candidateName.toLowerCase() !== name.toLowerCase()) {
19631953
continue;
19641954
}
1965-
// Only care about a result better than the best so far.
1966-
const distance = levenshteinWithMax(nameLowerCase, candidateNameLowerCase, bestDistance - 1);
1955+
1956+
const distance = levenshteinWithMax(name, candidateName, bestDistance - 0.1);
19671957
if (distance === undefined) {
19681958
continue;
19691959
}
1970-
if (distance < 3) {
1971-
justCheckExactMatches = true;
1972-
bestCandidate = candidate;
1973-
}
1974-
else {
1975-
Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined
1976-
bestDistance = distance;
1977-
bestCandidate = candidate;
1978-
}
1960+
1961+
Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined
1962+
bestDistance = distance;
1963+
bestCandidate = candidate;
19791964
}
19801965
}
19811966
return bestCandidate;
@@ -1985,26 +1970,30 @@ namespace ts {
19851970
let previous = new Array(s2.length + 1);
19861971
let current = new Array(s2.length + 1);
19871972
/** Represents any value > max. We don't care about the particular value. */
1988-
const big = max + 1;
1973+
const big = max + 0.01;
19891974

19901975
for (let i = 0; i <= s2.length; i++) {
19911976
previous[i] = i;
19921977
}
19931978

19941979
for (let i = 1; i <= s1.length; i++) {
19951980
const c1 = s1.charCodeAt(i - 1);
1996-
const minJ = i > max ? i - max : 1;
1997-
const maxJ = s2.length > max + i ? max + i : s2.length;
1981+
const minJ = Math.ceil(i > max ? i - max : 1);
1982+
const maxJ = Math.floor(s2.length > max + i ? max + i : s2.length);
19981983
current[0] = i;
19991984
/** Smallest value of the matrix in the ith column. */
20001985
let colMin = i;
20011986
for (let j = 1; j < minJ; j++) {
20021987
current[j] = big;
20031988
}
20041989
for (let j = minJ; j <= maxJ; j++) {
1990+
// case difference should be significantly cheaper than other differences
1991+
const substitutionDistance = s1[i - 1].toLowerCase() === s2[j-1].toLowerCase()
1992+
? (previous[j - 1] + 0.1)
1993+
: (previous[j - 1] + 2);
20051994
const dist = c1 === s2.charCodeAt(j - 1)
20061995
? previous[j - 1]
2007-
: Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ previous[j - 1] + 2);
1996+
: Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ substitutionDistance);
20081997
current[j] = dist;
20091998
colMin = Math.min(colMin, dist);
20101999
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////export let Console = 1;
4+
////export let console = 1;
5+
////[|conole|] = 1;
6+
7+
verify.rangeAfterCodeFix('console');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////export let console = 1;
4+
////export let Console = 1;
5+
////[|conole|] = 1;
6+
7+
verify.rangeAfterCodeFix('console');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////class Node {}
4+
////let node = new Node();
5+
////[|nodes|]
6+
7+
verify.rangeAfterCodeFix('node');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////let ABCDEFGHIJKLMNOPQR = 1;
4+
////let abcdefghijklmnopqrs = 1;
5+
////[|abcdefghijklmnopqr|]
6+
7+
verify.rangeAfterCodeFix('abcdefghijklmnopqrs');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////let ABCDEFGHI = 1;
4+
////let abcdefghij = 1;
5+
////[|abcdefghi|]
6+
7+
verify.rangeAfterCodeFix('ABCDEFGHI');
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////export let ab = 1;
4+
////[|aB|] = 1;
5+
6+
verify.rangeAfterCodeFix('ab');
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////export let ab = 1;
4+
////abc;
5+
6+
verify.not.codeFixAvailable();

0 commit comments

Comments
 (0)