diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 4b2ba7e4536..226fe74ef10 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts.GoToDefinition { - export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] { + export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] | undefined { const reference = getReferenceAtPosition(sourceFile, position, program); if (reference) { return [getDefinitionInfoForFileReference(reference.fileName, reference.file.fileName)]; @@ -29,7 +29,7 @@ namespace ts.GoToDefinition { // Could not find a symbol e.g. node is string or number keyword, // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol if (!symbol) { - return undefined; + return getDefinitionInfoForIndexSignatures(node, typeChecker); } // If this is an alias, and the request came at the declaration location @@ -157,6 +157,16 @@ namespace ts.GoToDefinition { return { definitions, textSpan }; } + // At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations. + function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined { + if (!isPropertyAccessExpression(node.parent) || node.parent.name !== node) return; + const type = checker.getTypeAtLocation(node.parent.expression); + return mapDefined(type.isUnionOrIntersection() ? type.types : [type], nonUnionType => { + const info = checker.getIndexInfoOfType(nonUnionType, IndexKind.String); + return info && info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration); + }); + } + // Go to the original declaration for cases: // // (1) when the aliased symbol was declared in the location(parent). diff --git a/tests/cases/fourslash/goToDefinitionIndexSignature.ts b/tests/cases/fourslash/goToDefinitionIndexSignature.ts new file mode 100644 index 00000000000..d5438830dc0 --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionIndexSignature.ts @@ -0,0 +1,15 @@ +/// + +////interface I { +//// /*defI*/[x: string]: boolean; +////} +////interface J { +//// /*defJ*/[x: string]: number; +////} +////declare const i: I; +////i.[|/*useI*/foo|]; +////declare const ij: I | J; +////ij.[|/*useIJ*/foo|]; + +verify.goToDefinition("useI", ["defI"]); +verify.goToDefinition("useIJ", ["defI", "defJ"]); diff --git a/tests/cases/fourslash/goToDefinitionIndexSignature2.ts b/tests/cases/fourslash/goToDefinitionIndexSignature2.ts new file mode 100644 index 00000000000..9188eaf67d0 --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionIndexSignature2.ts @@ -0,0 +1,11 @@ +/// + +// Tests that we don't crash for an index signature with no declaration. + +// @allowJs: true + +// @Filename: /a.js +////const o = {}; +////o.[|/*use*/foo|]; + +verify.goToDefinition("use", []); diff --git a/tests/cases/fourslash/goToDefinitionRest.ts b/tests/cases/fourslash/goToDefinitionRest.ts index 2577aafb625..4f1aa23c2ad 100644 --- a/tests/cases/fourslash/goToDefinitionRest.ts +++ b/tests/cases/fourslash/goToDefinitionRest.ts @@ -8,5 +8,5 @@ ////let t: Gen; ////var { x, ...rest } = t; ////rest.[|/*2*/parent|]; -const ranges = test.ranges(); + verify.goToDefinition('2', [ '1' ]);