diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts
index 06aea522ce9be..826dfe9eb591b 100644
--- a/src/services/findAllReferences.ts
+++ b/src/services/findAllReferences.ts
@@ -234,6 +234,7 @@ import {
StringLiteralLike,
stripQuotes,
SuperContainer,
+ SwitchStatement,
Symbol,
SymbolDisplay,
SymbolDisplayPart,
@@ -406,7 +407,7 @@ function getContextNodeForNodeEntry(node: Node): ContextNode | undefined {
}
/** @internal */
-export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined {
+export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | SwitchStatement | undefined): ContextNode | undefined {
if (!node) return undefined;
switch (node.kind) {
case SyntaxKind.VariableDeclaration:
@@ -451,14 +452,18 @@ export function getContextNode(node: NamedDeclaration | BinaryExpression | ForIn
findAncestor(node.parent, node => isBinaryExpression(node) || isForInOrOfStatement(node)) as BinaryExpression | ForInOrOfStatement,
) :
node;
-
+ case SyntaxKind.SwitchStatement:
+ return {
+ start: find(node.getChildren(node.getSourceFile()), node => node.kind === SyntaxKind.SwitchKeyword)!,
+ end: (node as SwitchStatement).caseBlock,
+ };
default:
return node;
}
}
/** @internal */
-export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan; } | undefined {
+export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context: ContextNode | undefined): { contextSpan: TextSpan; } | undefined {
if (!context) return undefined;
const contextSpan = isContextWithStartAndEndNode(context) ?
getTextSpan(context.start, sourceFile, context.end) :
@@ -874,6 +879,9 @@ function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSp
start += 1;
end -= 1;
}
+ if (endNode?.kind === SyntaxKind.CaseBlock) {
+ end = endNode.getFullStart();
+ }
return createTextSpanFromBounds(start, end);
}
diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts
index c603c6c7bab3d..79afa66a4d3a6 100644
--- a/src/services/goToDefinition.ts
+++ b/src/services/goToDefinition.ts
@@ -53,6 +53,7 @@ import {
isClassStaticBlockDeclaration,
isConstructorDeclaration,
isDeclarationFileName,
+ isDefaultClause,
isExternalModuleNameRelative,
isFunctionLike,
isFunctionLikeDeclaration,
@@ -69,6 +70,7 @@ import {
isPropertyName,
isRightSideOfPropertyAccess,
isStaticModifier,
+ isSwitchStatement,
isTypeAliasDeclaration,
isTypeReferenceNode,
isVariableDeclaration,
@@ -91,6 +93,7 @@ import {
skipTrivia,
some,
SourceFile,
+ SwitchStatement,
Symbol,
SymbolDisplay,
SymbolFlags,
@@ -105,6 +108,9 @@ import {
TypeReference,
unescapeLeadingUnderscores,
} from "./_namespaces/ts";
+import {
+ isContextWithStartAndEndNode,
+} from "./_namespaces/ts.FindAllReferences";
/** @internal */
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined {
@@ -133,9 +139,26 @@ export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile
return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
}
- if (node.kind === SyntaxKind.ReturnKeyword) {
- const functionDeclaration = findAncestor(node.parent, n => isClassStaticBlockDeclaration(n) ? "quit" : isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined;
- return functionDeclaration ? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)] : undefined;
+ switch (node.kind) {
+ case SyntaxKind.ReturnKeyword:
+ const functionDeclaration = findAncestor(node.parent, n =>
+ isClassStaticBlockDeclaration(n)
+ ? "quit"
+ : isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined;
+ return functionDeclaration
+ ? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)]
+ : undefined;
+ case SyntaxKind.DefaultKeyword:
+ if (!isDefaultClause(node.parent)) {
+ break;
+ }
+ // falls through
+ case SyntaxKind.CaseKeyword:
+ const switchStatement = findAncestor(node.parent, isSwitchStatement);
+ if (switchStatement) {
+ return [createDefinitionInfoFromSwitch(switchStatement, sourceFile)];
+ }
+ break;
}
if (node.kind === SyntaxKind.AwaitKeyword) {
@@ -634,6 +657,24 @@ function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declara
};
}
+function createDefinitionInfoFromSwitch(statement: SwitchStatement, sourceFile: SourceFile): DefinitionInfo {
+ const keyword = FindAllReferences.getContextNode(statement)!;
+ const textSpan = createTextSpanFromNode(isContextWithStartAndEndNode(keyword) ? keyword.start : keyword, sourceFile);
+ return {
+ fileName: sourceFile.fileName,
+ textSpan,
+ kind: ScriptElementKind.keyword,
+ name: "switch",
+ containerKind: undefined!,
+ containerName: "",
+ ...FindAllReferences.toContextSpan(textSpan, sourceFile, keyword),
+ isLocal: true,
+ isAmbient: false,
+ unverified: false,
+ failedAliasResolution: undefined,
+ };
+}
+
function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean {
if (checker.isDeclarationVisible(declaration)) return true;
if (!declaration.parent) return false;
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase1.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase1.baseline.jsonc
new file mode 100644
index 0000000000000..c92107111f2a0
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase1.baseline.jsonc
@@ -0,0 +1,17 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase1.ts ===
+// <|[|switch|] (null )|> {
+// /*GOTO DEF*/case null: break;
+// }
+
+ // === Details ===
+ [
+ {
+ "kind": "keyword",
+ "name": "switch",
+ "containerName": "",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false
+ }
+ ]
\ No newline at end of file
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase2.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase2.baseline.jsonc
new file mode 100644
index 0000000000000..318e346d9398f
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase2.baseline.jsonc
@@ -0,0 +1,17 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase2.ts ===
+// <|[|switch|] (null)|> {
+// /*GOTO DEF*/default: break;
+// }
+
+ // === Details ===
+ [
+ {
+ "kind": "keyword",
+ "name": "switch",
+ "containerName": "",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false
+ }
+ ]
\ No newline at end of file
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase3.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase3.baseline.jsonc
new file mode 100644
index 0000000000000..022a654b71cdd
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase3.baseline.jsonc
@@ -0,0 +1,45 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase3.ts ===
+// <|[|switch|] (null)|> {
+// /*GOTO DEF*/default: {
+// switch (null) {
+// default: break;
+// }
+// };
+// }
+
+ // === Details ===
+ [
+ {
+ "kind": "keyword",
+ "name": "switch",
+ "containerName": "",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false
+ }
+ ]
+
+
+
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase3.ts ===
+// switch (null) {
+// default: {
+// <|[|switch|] (null)|> {
+// /*GOTO DEF*/default: break;
+// }
+// };
+// }
+
+ // === Details ===
+ [
+ {
+ "kind": "keyword",
+ "name": "switch",
+ "containerName": "",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false
+ }
+ ]
\ No newline at end of file
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase4.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase4.baseline.jsonc
new file mode 100644
index 0000000000000..12b7b46563064
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase4.baseline.jsonc
@@ -0,0 +1,21 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase4.ts ===
+// switch (null) {
+// case null: break;
+// }
+//
+// <|[|switch|] (null)|> {
+// /*GOTO DEF*/case null: break;
+// }
+
+ // === Details ===
+ [
+ {
+ "kind": "keyword",
+ "name": "switch",
+ "containerName": "",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false
+ }
+ ]
\ No newline at end of file
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase5.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase5.baseline.jsonc
new file mode 100644
index 0000000000000..1277d0b5dd945
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase5.baseline.jsonc
@@ -0,0 +1,16 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase5.ts ===
+// [|export /*GOTO DEF*/default {}|]
+
+ // === Details ===
+ [
+ {
+ "kind": "property",
+ "name": "default",
+ "containerName": "\"/tests/cases/fourslash/goToDefinitionSwitchCase5\"",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false,
+ "failedAliasResolution": false
+ }
+ ]
\ No newline at end of file
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase6.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase6.baseline.jsonc
new file mode 100644
index 0000000000000..3f6894203dc69
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase6.baseline.jsonc
@@ -0,0 +1,47 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase6.ts ===
+// export default { /*GOTO DEF*/[|{| textSpan: true |}case|] };
+// default;
+// case 42;
+
+ // === Details ===
+ [
+ {
+ "kind": "property",
+ "name": "case",
+ "containerName": "__object",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false,
+ "failedAliasResolution": false
+ }
+ ]
+
+
+
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase6.ts ===
+// [|export default { case };
+// /*GOTO DEF*/default;
+// case 42;|]
+
+ // === Details ===
+ [
+ {
+ "kind": "module",
+ "name": "\"/tests/cases/fourslash/goToDefinitionSwitchCase6\"",
+ "containerName": "",
+ "isLocal": false,
+ "isAmbient": false,
+ "unverified": false,
+ "failedAliasResolution": false
+ }
+ ]
+
+
+
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase6.ts ===
+// export default { case };
+// default;
+// /*GOTO DEF*/case 42;
\ No newline at end of file
diff --git a/tests/baselines/reference/goToDefinitionSwitchCase7.baseline.jsonc b/tests/baselines/reference/goToDefinitionSwitchCase7.baseline.jsonc
new file mode 100644
index 0000000000000..bf6c818dc552e
--- /dev/null
+++ b/tests/baselines/reference/goToDefinitionSwitchCase7.baseline.jsonc
@@ -0,0 +1,18 @@
+// === goToDefinition ===
+// === /tests/cases/fourslash/goToDefinitionSwitchCase7.ts ===
+// switch (null) {
+// case null:
+// [|export /*GOTO DEF*/default 123;|]
+
+ // === Details ===
+ [
+ {
+ "kind": "var",
+ "name": "default",
+ "containerName": "",
+ "isLocal": true,
+ "isAmbient": false,
+ "unverified": false,
+ "failedAliasResolution": false
+ }
+ ]
\ No newline at end of file
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase1.ts b/tests/cases/fourslash/goToDefinitionSwitchCase1.ts
new file mode 100644
index 0000000000000..86a695f1ecf3b
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase1.ts
@@ -0,0 +1,7 @@
+///
+
+////switch (null ) {
+//// [|/*start*/case|] null: break;
+////}
+
+verify.baselineGoToDefinition("start");
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase2.ts b/tests/cases/fourslash/goToDefinitionSwitchCase2.ts
new file mode 100644
index 0000000000000..3d480dd198afb
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase2.ts
@@ -0,0 +1,7 @@
+///
+
+////switch (null) {
+//// [|/*start*/default|]: break;
+////}
+
+verify.baselineGoToDefinition("start");
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase3.ts b/tests/cases/fourslash/goToDefinitionSwitchCase3.ts
new file mode 100644
index 0000000000000..0dce0e7c153fc
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase3.ts
@@ -0,0 +1,11 @@
+///
+
+////switch (null) {
+//// [|/*start1*/default|]: {
+//// switch (null) {
+//// [|/*start2*/default|]: break;
+//// }
+//// };
+////}
+
+verify.baselineGoToDefinition("start1", "start2");
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase4.ts b/tests/cases/fourslash/goToDefinitionSwitchCase4.ts
new file mode 100644
index 0000000000000..db4b801415dc2
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase4.ts
@@ -0,0 +1,11 @@
+///
+
+//// switch (null) {
+//// case null: break;
+//// }
+////
+//// switch (null) {
+//// [|/*start*/case|] null: break;
+//// }
+
+verify.baselineGoToDefinition("start");
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase5.ts b/tests/cases/fourslash/goToDefinitionSwitchCase5.ts
new file mode 100644
index 0000000000000..83e8f28ad0aab
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase5.ts
@@ -0,0 +1,5 @@
+///
+
+////export [|/*start*/default|] {}
+
+verify.baselineGoToDefinition("start");
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase6.ts b/tests/cases/fourslash/goToDefinitionSwitchCase6.ts
new file mode 100644
index 0000000000000..b6ac5daf8f5f8
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase6.ts
@@ -0,0 +1,7 @@
+///
+
+////export default { [|/*a*/case|] };
+////[|/*b*/default|];
+////[|/*c*/case|] 42;
+
+verify.baselineGoToDefinition("a", "b", "c");
diff --git a/tests/cases/fourslash/goToDefinitionSwitchCase7.ts b/tests/cases/fourslash/goToDefinitionSwitchCase7.ts
new file mode 100644
index 0000000000000..244b4b0b6a726
--- /dev/null
+++ b/tests/cases/fourslash/goToDefinitionSwitchCase7.ts
@@ -0,0 +1,7 @@
+///
+
+////switch (null) {
+//// case null:
+//// export [|/*start*/default|] 123;
+
+verify.baselineGoToDefinition("start");