@@ -204,7 +204,9 @@ namespace ts {
204
204
// since we are only interested in declarations of the module itself
205
205
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false);
206
206
},
207
- getApparentType
207
+ getApparentType,
208
+ getSuggestionForNonexistentProperty,
209
+ getSuggestionForNonexistentSymbol,
208
210
};
209
211
210
212
const tupleTypes: GenericType[] = [];
@@ -840,7 +842,25 @@ namespace ts {
840
842
// Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and
841
843
// the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with
842
844
// the given name can be found.
843
- function resolveName(location: Node | undefined, name: string, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage, nameArg: string | Identifier): Symbol {
845
+ function resolveName(
846
+ location: Node | undefined,
847
+ name: string,
848
+ meaning: SymbolFlags,
849
+ nameNotFoundMessage: DiagnosticMessage,
850
+ nameArg: string | Identifier,
851
+ suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol {
852
+ return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, getSymbol, suggestedNameNotFoundMessage);
853
+ }
854
+
855
+ function resolveNameHelper(
856
+ location: Node | undefined,
857
+ name: string,
858
+ meaning: SymbolFlags,
859
+ nameNotFoundMessage: DiagnosticMessage,
860
+ nameArg: string | Identifier,
861
+ lookup: (symbols: SymbolTable, name: string, meaning: SymbolFlags) => Symbol,
862
+ suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol {
863
+ const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location
844
864
let result: Symbol;
845
865
let lastLocation: Node;
846
866
let propertyWithInvalidInitializer: Node;
@@ -851,7 +871,7 @@ namespace ts {
851
871
loop: while (location) {
852
872
// Locals of a source file are not in scope (because they get merged into the global symbol table)
853
873
if (location.locals && !isGlobalSourceFile(location)) {
854
- if (result = getSymbol (location.locals, name, meaning)) {
874
+ if (result = lookup (location.locals, name, meaning)) {
855
875
let useResult = true;
856
876
if (isFunctionLike(location) && lastLocation && lastLocation !== (<FunctionLikeDeclaration>location).body) {
857
877
// symbol lookup restrictions for function-like declarations
@@ -929,12 +949,12 @@ namespace ts {
929
949
}
930
950
}
931
951
932
- if (result = getSymbol (moduleExports, name, meaning & SymbolFlags.ModuleMember)) {
952
+ if (result = lookup (moduleExports, name, meaning & SymbolFlags.ModuleMember)) {
933
953
break loop;
934
954
}
935
955
break;
936
956
case SyntaxKind.EnumDeclaration:
937
- if (result = getSymbol (getSymbolOfNode(location).exports, name, meaning & SymbolFlags.EnumMember)) {
957
+ if (result = lookup (getSymbolOfNode(location).exports, name, meaning & SymbolFlags.EnumMember)) {
938
958
break loop;
939
959
}
940
960
break;
@@ -949,7 +969,7 @@ namespace ts {
949
969
if (isClassLike(location.parent) && !(getModifierFlags(location) & ModifierFlags.Static)) {
950
970
const ctor = findConstructorDeclaration(<ClassLikeDeclaration>location.parent);
951
971
if (ctor && ctor.locals) {
952
- if (getSymbol (ctor.locals, name, meaning & SymbolFlags.Value)) {
972
+ if (lookup (ctor.locals, name, meaning & SymbolFlags.Value)) {
953
973
// Remember the property node, it will be used later to report appropriate error
954
974
propertyWithInvalidInitializer = location;
955
975
}
@@ -959,7 +979,7 @@ namespace ts {
959
979
case SyntaxKind.ClassDeclaration:
960
980
case SyntaxKind.ClassExpression:
961
981
case SyntaxKind.InterfaceDeclaration:
962
- if (result = getSymbol (getSymbolOfNode(location).members, name, meaning & SymbolFlags.Type)) {
982
+ if (result = lookup (getSymbolOfNode(location).members, name, meaning & SymbolFlags.Type)) {
963
983
if (!isTypeParameterSymbolDeclaredInContainer(result, location)) {
964
984
// ignore type parameters not declared in this container
965
985
result = undefined;
@@ -995,7 +1015,7 @@ namespace ts {
995
1015
grandparent = location.parent.parent;
996
1016
if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) {
997
1017
// A reference to this grandparent's type parameters would be an error
998
- if (result = getSymbol (getSymbolOfNode(grandparent).members, name, meaning & SymbolFlags.Type)) {
1018
+ if (result = lookup (getSymbolOfNode(grandparent).members, name, meaning & SymbolFlags.Type)) {
999
1019
error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type);
1000
1020
return undefined;
1001
1021
}
@@ -1059,7 +1079,7 @@ namespace ts {
1059
1079
}
1060
1080
1061
1081
if (!result) {
1062
- result = getSymbol (globals, name, meaning);
1082
+ result = lookup (globals, name, meaning);
1063
1083
}
1064
1084
1065
1085
if (!result) {
@@ -1070,7 +1090,16 @@ namespace ts {
1070
1090
!checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) &&
1071
1091
!checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) &&
1072
1092
!checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning)) {
1073
- error(errorLocation, nameNotFoundMessage, typeof nameArg === "string" ? nameArg : declarationNameToString(nameArg));
1093
+ let suggestion: string | undefined;
1094
+ if (suggestedNameNotFoundMessage) {
1095
+ suggestion = getSuggestionForNonexistentSymbol(originalLocation, name, meaning);
1096
+ if (suggestion) {
1097
+ error(errorLocation, suggestedNameNotFoundMessage, typeof nameArg === "string" ? nameArg : declarationNameToString(nameArg), suggestion);
1098
+ }
1099
+ }
1100
+ if (!suggestion) {
1101
+ error(errorLocation, nameNotFoundMessage, typeof nameArg === "string" ? nameArg : declarationNameToString(nameArg));
1102
+ }
1074
1103
}
1075
1104
}
1076
1105
return undefined;
@@ -10411,7 +10440,7 @@ namespace ts {
10411
10440
function getResolvedSymbol(node: Identifier): Symbol {
10412
10441
const links = getNodeLinks(node);
10413
10442
if (!links.resolvedSymbol) {
10414
- links.resolvedSymbol = !nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node) || unknownSymbol;
10443
+ links.resolvedSymbol = !nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node, Diagnostics.Cannot_find_name_0_Did_you_mean_1 ) || unknownSymbol;
10415
10444
}
10416
10445
return links.resolvedSymbol;
10417
10446
}
@@ -14051,44 +14080,6 @@ namespace ts {
14051
14080
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
14052
14081
}
14053
14082
14054
- function reportNonexistentProperty(propNode: Identifier, containingType: Type) {
14055
- let errorInfo: DiagnosticMessageChain;
14056
- if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
14057
- for (const subtype of (containingType as UnionType).types) {
14058
- if (!getPropertyOfType(subtype, propNode.text)) {
14059
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype));
14060
- break;
14061
- }
14062
- }
14063
- }
14064
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
14065
- diagnostics.add(createDiagnosticForNodeFromMessageChain(propNode, errorInfo));
14066
- }
14067
-
14068
- function markPropertyAsReferenced(prop: Symbol) {
14069
- if (prop &&
14070
- noUnusedIdentifiers &&
14071
- (prop.flags & SymbolFlags.ClassMember) &&
14072
- prop.valueDeclaration && (getModifierFlags(prop.valueDeclaration) & ModifierFlags.Private)) {
14073
- if (getCheckFlags(prop) & CheckFlags.Instantiated) {
14074
- getSymbolLinks(prop).target.isReferenced = true;
14075
- }
14076
- else {
14077
- prop.isReferenced = true;
14078
- }
14079
- }
14080
- }
14081
-
14082
- function isInPropertyInitializer(node: Node): boolean {
14083
- while (node) {
14084
- if (node.parent && node.parent.kind === SyntaxKind.PropertyDeclaration && (node.parent as PropertyDeclaration).initializer === node) {
14085
- return true;
14086
- }
14087
- node = node.parent;
14088
- }
14089
- return false;
14090
- }
14091
-
14092
14083
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
14093
14084
const type = checkNonNullExpression(left);
14094
14085
if (isTypeAny(type) || type === silentNeverType) {
@@ -14152,6 +14143,116 @@ namespace ts {
14152
14143
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
14153
14144
}
14154
14145
14146
+ function reportNonexistentProperty(propNode: Identifier, containingType: Type) {
14147
+ let errorInfo: DiagnosticMessageChain;
14148
+ if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
14149
+ for (const subtype of (containingType as UnionType).types) {
14150
+ if (!getPropertyOfType(subtype, propNode.text)) {
14151
+ errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype));
14152
+ break;
14153
+ }
14154
+ }
14155
+ }
14156
+ const suggestion = getSuggestionForNonexistentProperty(propNode, containingType);
14157
+ if (suggestion) {
14158
+ errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestion);
14159
+ }
14160
+ else {
14161
+ errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
14162
+ }
14163
+ diagnostics.add(createDiagnosticForNodeFromMessageChain(propNode, errorInfo));
14164
+ }
14165
+
14166
+ function getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined {
14167
+ const suggestion = getSpellingSuggestionForName(node.text, getPropertiesOfObjectType(containingType), SymbolFlags.Value);
14168
+ return suggestion && suggestion.name;
14169
+ }
14170
+
14171
+ function getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string {
14172
+ const result = resolveNameHelper(location, name, meaning, /*nameNotFoundMessage*/ undefined, name, (symbols, name, meaning) => {
14173
+ const symbol = getSymbol(symbols, name, meaning);
14174
+ if (symbol) {
14175
+ // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function
14176
+ // So the table *contains* `x` but `x` isn't actually in scope.
14177
+ // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion.
14178
+ return symbol;
14179
+ }
14180
+ return getSpellingSuggestionForName(name, arrayFrom(symbols.values()), meaning);
14181
+ });
14182
+ if (result) {
14183
+ return result.name;
14184
+ }
14185
+ }
14186
+
14187
+ /**
14188
+ * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
14189
+ * Names less than length 3 only check for case-insensitive equality, not levenshtein distance.
14190
+ *
14191
+ * If there is a candidate that's the same except for case, return that.
14192
+ * If there is a candidate that's within one edit of the name, return that.
14193
+ * Otherwise, return the candidate with the smallest Levenshtein distance,
14194
+ * except for candidates:
14195
+ * * With no name
14196
+ * * Whose meaning doesn't match the `meaning` parameter.
14197
+ * * Whose length differs from the target name by more than 3.
14198
+ * * Whose levenshtein distance is more than 0.7 of the length of the name
14199
+ * (0.7 allows identifiers of length 3 to have a distance of 2 to allow for one substitution)
14200
+ * Names longer than 30 characters don't get suggestions because Levenshtein distance is an n**2 algorithm.
14201
+ */
14202
+ function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined {
14203
+ const worstDistance = name.length * 0.7;
14204
+ let bestDistance = Number.MAX_VALUE;
14205
+ let bestCandidate = undefined;
14206
+ if (name.length > 30) {
14207
+ return undefined;
14208
+ }
14209
+ name = name.toLowerCase();
14210
+ for (const candidate of symbols) {
14211
+ if (candidate.flags & meaning && candidate.name && Math.abs(candidate.name.length - name.length) < 4) {
14212
+ const candidateName = candidate.name.toLowerCase();
14213
+ if (candidateName === name) {
14214
+ return candidate;
14215
+ }
14216
+ if (candidateName.length < 3) {
14217
+ continue;
14218
+ }
14219
+ const distance = levenshtein(candidateName, name);
14220
+ if (distance < 2) {
14221
+ return candidate;
14222
+ }
14223
+ else if (distance < bestDistance && distance < worstDistance) {
14224
+ bestDistance = distance;
14225
+ bestCandidate = candidate;
14226
+ }
14227
+ }
14228
+ }
14229
+ return bestCandidate;
14230
+ }
14231
+
14232
+ function markPropertyAsReferenced(prop: Symbol) {
14233
+ if (prop &&
14234
+ noUnusedIdentifiers &&
14235
+ (prop.flags & SymbolFlags.ClassMember) &&
14236
+ prop.valueDeclaration && (getModifierFlags(prop.valueDeclaration) & ModifierFlags.Private)) {
14237
+ if (getCheckFlags(prop) & CheckFlags.Instantiated) {
14238
+ getSymbolLinks(prop).target.isReferenced = true;
14239
+ }
14240
+ else {
14241
+ prop.isReferenced = true;
14242
+ }
14243
+ }
14244
+ }
14245
+
14246
+ function isInPropertyInitializer(node: Node): boolean {
14247
+ while (node) {
14248
+ if (node.parent && node.parent.kind === SyntaxKind.PropertyDeclaration && (node.parent as PropertyDeclaration).initializer === node) {
14249
+ return true;
14250
+ }
14251
+ node = node.parent;
14252
+ }
14253
+ return false;
14254
+ }
14255
+
14155
14256
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {
14156
14257
const left = node.kind === SyntaxKind.PropertyAccessExpression
14157
14258
? (<PropertyAccessExpression>node).expression
0 commit comments