Skip to content

Link back to likely declarations in "did you mean...?" error messages #25477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 6, 2018
2,363 changes: 1,182 additions & 1,181 deletions package-lock.json

Large diffs are not rendered by default.

75 changes: 51 additions & 24 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ namespace ts {
getAllPossiblePropertiesOfTypes,
getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type),
getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
getSuggestionForNonexistentModule: (node, target) => getSuggestionForNonexistentModule(node, target),
getSuggestionForNonexistentExport: (node, target) => getSuggestionForNonexistentExport(node, target),
getBaseConstraintOfType,
getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined,
resolveName(name, location, meaning, excludeGlobals) {
Expand Down Expand Up @@ -710,7 +710,7 @@ namespace ts {
}
}

function addRelatedInfo(diagnostic: Diagnostic, ...relatedInformation: DiagnosticRelatedInformation[]) {
function addRelatedInfo(diagnostic: Diagnostic, ...relatedInformation: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]]) {
if (!diagnostic.relatedInformation) {
diagnostic.relatedInformation = [];
}
Expand Down Expand Up @@ -1434,11 +1434,18 @@ namespace ts {
!checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) &&
!checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) &&
!checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning)) {
let suggestion: string | undefined;
let suggestion: Symbol | undefined;
if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) {
suggestion = getSuggestionForNonexistentSymbol(originalLocation, name, meaning);
suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning);
if (suggestion) {
error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestion);
const suggestionName = symbolToString(suggestion);
const diagnostic = error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestionName);
if (suggestion.valueDeclaration) {
addRelatedInfo(
diagnostic,
createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)
);
}
}
}
if (!suggestion) {
Expand Down Expand Up @@ -1674,7 +1681,7 @@ namespace ts {

if (diagnosticMessage) {
addRelatedInfo(diagnosticMessage,
createDiagnosticForNode(declaration, Diagnostics._0_was_declared_here, declarationName)
createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName)
);
}
}
Expand Down Expand Up @@ -1866,9 +1873,15 @@ namespace ts {
if (!symbol) {
const moduleName = getFullyQualifiedName(moduleSymbol);
const declarationName = declarationNameToString(name);
const suggestion = getSuggestionForNonexistentModule(name, targetSymbol);
const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol);
if (suggestion !== undefined) {
error(name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_2, moduleName, declarationName, suggestion);
const suggestionName = symbolToString(suggestion);
const diagnostic = error(name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_2, moduleName, declarationName, suggestionName);
if (suggestion.valueDeclaration) {
addRelatedInfo(diagnostic,
createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)
);
}
}
else {
error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName);
Expand Down Expand Up @@ -17661,7 +17674,7 @@ namespace ts {

if (diagnosticMessage) {
addRelatedInfo(diagnosticMessage,
createDiagnosticForNode(valueDeclaration, Diagnostics._0_was_declared_here, declarationName)
createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)
);
}
}
Expand Down Expand Up @@ -17711,6 +17724,7 @@ namespace ts {

function reportNonexistentProperty(propNode: Identifier, containingType: Type) {
let errorInfo: DiagnosticMessageChain | undefined;
let relatedInfo: Diagnostic | undefined;
if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
for (const subtype of (containingType as UnionType).types) {
if (!getPropertyOfType(subtype, propNode.escapedText)) {
Expand All @@ -17724,30 +17738,34 @@ namespace ts {
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_forget_to_use_await, declarationNameToString(propNode), typeToString(containingType));
}
else {
const suggestion = getSuggestionForNonexistentProperty(propNode, containingType);
const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType);
if (suggestion !== undefined) {
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestion);
const suggestedName = symbolName(suggestion);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestedName);
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
}
else {
const suggestion = getSuggestionForNonexistentProperty(propNode, containingType);
if (suggestion !== undefined) {
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestion);
}
else {
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
}
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
}
}

diagnostics.add(createDiagnosticForNodeFromMessageChain(propNode, errorInfo));
const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo);
if (relatedInfo) {
addRelatedInfo(resultDiagnostic, relatedInfo);
}
diagnostics.add(resultDiagnostic);
}

function getSuggestedSymbolForNonexistentProperty(name: Identifier | string, containingType: Type): Symbol | undefined {
return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
}

function getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined {
const suggestion = getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType);
return suggestion && symbolName(suggestion);
}

function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined {
function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined {
Debug.assert(outerName !== undefined, "outername should always be defined");
const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => {
Debug.assertEqual(outerName, name, "name should equal outerName");
Expand All @@ -17757,11 +17775,20 @@ namespace ts {
// However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion.
return symbol || getSpellingSuggestionForName(unescapeLeadingUnderscores(name), arrayFrom(symbols.values()), meaning);
});
return result && symbolName(result);
return result;
}

function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have made these all internal :(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. 😡

We still can?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is a breaking change..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but yes we can

const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning);
return symbolResult && symbolName(symbolResult);
}

function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined {
return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember);
}

function getSuggestionForNonexistentModule(name: Identifier, targetModule: Symbol): string | undefined {
const suggestion = targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember);
function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined {
const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule);
return suggestion && symbolName(suggestion);
}

Expand Down
6 changes: 3 additions & 3 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2401,8 +2401,8 @@
"category": "Error",
"code": 2727
},
"'{0}' was declared here.": {
"category": "Error",
"'{0}' is declared here.": {
"category": "Message",
"code": 2728
},
"Property '{0}' is used before its initialization.": {
Expand Down Expand Up @@ -3886,7 +3886,7 @@
"code": 7037
},
"Type originates at this import. A namespace-style import cannot be called or constructed, and will cause a failure at runtime. Consider using a default import or import require here instead.": {
"category": "Error",
"category": "Message",
"code": 7038
},
"Mapped object type implicitly has an 'any' template type.": {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2996,7 +2996,7 @@ namespace ts {
getApparentType(type: Type): Type;
getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined;
getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
getSuggestionForNonexistentModule(node: Identifier, target: Symbol): string | undefined;
getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined;
getBaseConstraintOfType(type: Type): Type | undefined;
getDefaultFromTypeParameter(type: Type): Type | undefined;

Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/fixSpelling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace ts.codefix {
const importDeclaration = findAncestor(node, isImportDeclaration)!;
const resolvedSourceFile = getResolvedSourceFileFromImportDeclaration(sourceFile, context, importDeclaration);
if (resolvedSourceFile && resolvedSourceFile.symbol) {
suggestion = checker.getSuggestionForNonexistentModule(node as Identifier, resolvedSourceFile.symbol);
suggestion = checker.getSuggestionForNonexistentExport(node as Identifier, resolvedSourceFile.symbol);
}
}
else {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5For-of17.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ tests/cases/conformance/statements/for-ofStatements/ES5For-of17.ts(3,20): error
for (let v of [v]) {
~
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
!!! related TS2728 tests/cases/conformance/statements/for-ofStatements/ES5For-of17.ts:3:14: 'v' was declared here.
!!! related TS2728 tests/cases/conformance/statements/for-ofStatements/ES5For-of17.ts:3:14: 'v' is declared here.
var x = v;
v++;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5For-of20.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ tests/cases/conformance/statements/for-ofStatements/ES5For-of20.ts(4,15): error
for (let v of [v]) {
~
!!! error TS2448: Block-scoped variable 'v' used before its declaration.
!!! related TS2728 tests/cases/conformance/statements/for-ofStatements/ES5For-of20.ts:3:14: 'v' was declared here.
!!! related TS2728 tests/cases/conformance/statements/for-ofStatements/ES5For-of20.ts:3:14: 'v' is declared here.
const v;
~
!!! error TS1155: 'const' declarations must be initialized.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ tests/cases/conformance/internalModules/DeclarationMerging/simple.ts(2,31): erro
export var Instance = new A();
~
!!! error TS2449: Class 'A' used before its declaration.
!!! related TS2728 tests/cases/conformance/internalModules/DeclarationMerging/simple.ts:6:7: 'A' was declared here.
!!! related TS2728 tests/cases/conformance/internalModules/DeclarationMerging/simple.ts:6:7: 'A' is declared here.
}

// duplicate identifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ tests/cases/conformance/internalModules/exportDeclarations/ModuleWithExportedAnd
!!! error TS2339: Property 'fn2' does not exist on type 'typeof A'.
var fng2 = A.fng2;
~~~~
!!! error TS2551: Property 'fng2' does not exist on type 'typeof A'. Did you mean 'fng'?
!!! error TS2551: Property 'fng2' does not exist on type 'typeof A'. Did you mean 'fng'?
!!! related TS2728 tests/cases/conformance/internalModules/exportDeclarations/ModuleWithExportedAndNonExportedFunctions.ts:7:21: 'fng' is declared here.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ tests/cases/compiler/anonymousClassExpression2.ts(13,18): error TS2551: Property
this.methodA; // error
~~~~~~~
!!! error TS2551: Property 'methodA' does not exist on type 'B'. Did you mean 'methodB'?
!!! related TS2728 tests/cases/compiler/anonymousClassExpression2.ts:12:9: 'methodB' is declared here.
this.methodB; // ok
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2675,7 +2675,7 @@ declare namespace ts {
getApparentType(type: Type): Type;
getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined;
getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
getSuggestionForNonexistentModule(node: Identifier, target: Symbol): string | undefined;
getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined;
getBaseConstraintOfType(type: Type): Type | undefined;
getDefaultFromTypeParameter(type: Type): Type | undefined;
getAnyType(): Type;
Expand Down Expand Up @@ -5430,7 +5430,7 @@ declare namespace ts {
Class_name_cannot_be_Object_when_targeting_ES5_with_module_0: DiagnosticMessage;
Cannot_find_lib_definition_for_0: DiagnosticMessage;
Cannot_find_lib_definition_for_0_Did_you_mean_1: DiagnosticMessage;
_0_was_declared_here: DiagnosticMessage;
_0_is_declared_here: DiagnosticMessage;
Property_0_is_used_before_its_initialization: DiagnosticMessage;
Import_declaration_0_is_using_private_name_1: DiagnosticMessage;
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: DiagnosticMessage;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1920,7 +1920,7 @@ declare namespace ts {
getApparentType(type: Type): Type;
getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined;
getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
getSuggestionForNonexistentModule(node: Identifier, target: Symbol): string | undefined;
getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined;
getBaseConstraintOfType(type: Type): Type | undefined;
getDefaultFromTypeParameter(type: Type): Type | undefined;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ tests/cases/compiler/assignmentRestElementWithErrorSourceType.ts(2,10): error TS
~
!!! error TS2304: Cannot find name 'c'.
~~~~~
!!! error TS2552: Cannot find name 'tupel'. Did you mean 'tuple'?
!!! error TS2552: Cannot find name 'tupel'. Did you mean 'tuple'?
!!! related TS2728 tests/cases/compiler/assignmentRestElementWithErrorSourceType.ts:1:5: 'tuple' is declared here.
1 change: 1 addition & 0 deletions tests/baselines/reference/baseCheck.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tests/cases/compiler/baseCheck.ts(26,9): error TS2304: Cannot find name 'x'.
super(0, loc);
~~~
!!! error TS2552: Cannot find name 'loc'. Did you mean 'ELoc'?
!!! related TS2728 tests/cases/compiler/baseCheck.ts:2:7: 'ELoc' is declared here.
}

m() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2448: Bloc
for (let {[a]: a} of [{ }]) continue;
~
!!! error TS2448: Block-scoped variable 'a' used before its declaration.
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:2:16: 'a' was declared here.
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:2:16: 'a' is declared here.

// 2:
for (let {[a]: a} = { }; false; ) continue;
~
!!! error TS2448: Block-scoped variable 'a' used before its declaration.
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:5:16: 'a' was declared here.
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:5:16: 'a' is declared here.

// 3:
let {[b]: b} = { };
~
!!! error TS2448: Block-scoped variable 'b' used before its declaration.
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:8:11: 'b' was declared here.
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:8:11: 'b' is declared here.
Loading