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' ]);