Skip to content

Commit 1717826

Browse files
sviat9440sandersn
andauthored
fix(51225): Go-to-definition on case or default should jump to the containing switch statement if available. (#51236)
Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
1 parent 9a0c7b1 commit 1717826

16 files changed

+291
-6
lines changed

src/services/findAllReferences.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ import {
234234
StringLiteralLike,
235235
stripQuotes,
236236
SuperContainer,
237+
SwitchStatement,
237238
Symbol,
238239
SymbolDisplay,
239240
SymbolDisplayPart,
@@ -406,7 +407,7 @@ function getContextNodeForNodeEntry(node: Node): ContextNode | undefined {
406407
}
407408

408409
/** @internal */
409-
export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined {
410+
export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | SwitchStatement | undefined): ContextNode | undefined {
410411
if (!node) return undefined;
411412
switch (node.kind) {
412413
case SyntaxKind.VariableDeclaration:
@@ -451,14 +452,18 @@ export function getContextNode(node: NamedDeclaration | BinaryExpression | ForIn
451452
findAncestor(node.parent, node => isBinaryExpression(node) || isForInOrOfStatement(node)) as BinaryExpression | ForInOrOfStatement,
452453
) :
453454
node;
454-
455+
case SyntaxKind.SwitchStatement:
456+
return {
457+
start: find(node.getChildren(node.getSourceFile()), node => node.kind === SyntaxKind.SwitchKeyword)!,
458+
end: (node as SwitchStatement).caseBlock,
459+
};
455460
default:
456461
return node;
457462
}
458463
}
459464

460465
/** @internal */
461-
export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan; } | undefined {
466+
export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context: ContextNode | undefined): { contextSpan: TextSpan; } | undefined {
462467
if (!context) return undefined;
463468
const contextSpan = isContextWithStartAndEndNode(context) ?
464469
getTextSpan(context.start, sourceFile, context.end) :
@@ -874,6 +879,9 @@ function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSp
874879
start += 1;
875880
end -= 1;
876881
}
882+
if (endNode?.kind === SyntaxKind.CaseBlock) {
883+
end = endNode.getFullStart();
884+
}
877885
return createTextSpanFromBounds(start, end);
878886
}
879887

src/services/goToDefinition.ts

+44-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
isClassStaticBlockDeclaration,
5454
isConstructorDeclaration,
5555
isDeclarationFileName,
56+
isDefaultClause,
5657
isExternalModuleNameRelative,
5758
isFunctionLike,
5859
isFunctionLikeDeclaration,
@@ -69,6 +70,7 @@ import {
6970
isPropertyName,
7071
isRightSideOfPropertyAccess,
7172
isStaticModifier,
73+
isSwitchStatement,
7274
isTypeAliasDeclaration,
7375
isTypeReferenceNode,
7476
isVariableDeclaration,
@@ -91,6 +93,7 @@ import {
9193
skipTrivia,
9294
some,
9395
SourceFile,
96+
SwitchStatement,
9497
Symbol,
9598
SymbolDisplay,
9699
SymbolFlags,
@@ -105,6 +108,9 @@ import {
105108
TypeReference,
106109
unescapeLeadingUnderscores,
107110
} from "./_namespaces/ts";
111+
import {
112+
isContextWithStartAndEndNode,
113+
} from "./_namespaces/ts.FindAllReferences";
108114

109115
/** @internal */
110116
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
133139
return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
134140
}
135141

136-
if (node.kind === SyntaxKind.ReturnKeyword) {
137-
const functionDeclaration = findAncestor(node.parent, n => isClassStaticBlockDeclaration(n) ? "quit" : isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined;
138-
return functionDeclaration ? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)] : undefined;
142+
switch (node.kind) {
143+
case SyntaxKind.ReturnKeyword:
144+
const functionDeclaration = findAncestor(node.parent, n =>
145+
isClassStaticBlockDeclaration(n)
146+
? "quit"
147+
: isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined;
148+
return functionDeclaration
149+
? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)]
150+
: undefined;
151+
case SyntaxKind.DefaultKeyword:
152+
if (!isDefaultClause(node.parent)) {
153+
break;
154+
}
155+
// falls through
156+
case SyntaxKind.CaseKeyword:
157+
const switchStatement = findAncestor(node.parent, isSwitchStatement);
158+
if (switchStatement) {
159+
return [createDefinitionInfoFromSwitch(switchStatement, sourceFile)];
160+
}
161+
break;
139162
}
140163

141164
if (node.kind === SyntaxKind.AwaitKeyword) {
@@ -634,6 +657,24 @@ function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declara
634657
};
635658
}
636659

660+
function createDefinitionInfoFromSwitch(statement: SwitchStatement, sourceFile: SourceFile): DefinitionInfo {
661+
const keyword = FindAllReferences.getContextNode(statement)!;
662+
const textSpan = createTextSpanFromNode(isContextWithStartAndEndNode(keyword) ? keyword.start : keyword, sourceFile);
663+
return {
664+
fileName: sourceFile.fileName,
665+
textSpan,
666+
kind: ScriptElementKind.keyword,
667+
name: "switch",
668+
containerKind: undefined!,
669+
containerName: "",
670+
...FindAllReferences.toContextSpan(textSpan, sourceFile, keyword),
671+
isLocal: true,
672+
isAmbient: false,
673+
unverified: false,
674+
failedAliasResolution: undefined,
675+
};
676+
}
677+
637678
function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean {
638679
if (checker.isDeclarationVisible(declaration)) return true;
639680
if (!declaration.parent) return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase1.ts ===
3+
// <|[|switch|] (null )|> {
4+
// /*GOTO DEF*/case null: break;
5+
// }
6+
7+
// === Details ===
8+
[
9+
{
10+
"kind": "keyword",
11+
"name": "switch",
12+
"containerName": "",
13+
"isLocal": true,
14+
"isAmbient": false,
15+
"unverified": false
16+
}
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase2.ts ===
3+
// <|[|switch|] (null)|> {
4+
// /*GOTO DEF*/default: break;
5+
// }
6+
7+
// === Details ===
8+
[
9+
{
10+
"kind": "keyword",
11+
"name": "switch",
12+
"containerName": "",
13+
"isLocal": true,
14+
"isAmbient": false,
15+
"unverified": false
16+
}
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase3.ts ===
3+
// <|[|switch|] (null)|> {
4+
// /*GOTO DEF*/default: {
5+
// switch (null) {
6+
// default: break;
7+
// }
8+
// };
9+
// }
10+
11+
// === Details ===
12+
[
13+
{
14+
"kind": "keyword",
15+
"name": "switch",
16+
"containerName": "",
17+
"isLocal": true,
18+
"isAmbient": false,
19+
"unverified": false
20+
}
21+
]
22+
23+
24+
25+
// === goToDefinition ===
26+
// === /tests/cases/fourslash/goToDefinitionSwitchCase3.ts ===
27+
// switch (null) {
28+
// default: {
29+
// <|[|switch|] (null)|> {
30+
// /*GOTO DEF*/default: break;
31+
// }
32+
// };
33+
// }
34+
35+
// === Details ===
36+
[
37+
{
38+
"kind": "keyword",
39+
"name": "switch",
40+
"containerName": "",
41+
"isLocal": true,
42+
"isAmbient": false,
43+
"unverified": false
44+
}
45+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase4.ts ===
3+
// switch (null) {
4+
// case null: break;
5+
// }
6+
//
7+
// <|[|switch|] (null)|> {
8+
// /*GOTO DEF*/case null: break;
9+
// }
10+
11+
// === Details ===
12+
[
13+
{
14+
"kind": "keyword",
15+
"name": "switch",
16+
"containerName": "",
17+
"isLocal": true,
18+
"isAmbient": false,
19+
"unverified": false
20+
}
21+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase5.ts ===
3+
// [|export /*GOTO DEF*/default {}|]
4+
5+
// === Details ===
6+
[
7+
{
8+
"kind": "property",
9+
"name": "default",
10+
"containerName": "\"/tests/cases/fourslash/goToDefinitionSwitchCase5\"",
11+
"isLocal": true,
12+
"isAmbient": false,
13+
"unverified": false,
14+
"failedAliasResolution": false
15+
}
16+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase6.ts ===
3+
// export default { /*GOTO DEF*/[|{| textSpan: true |}case|] };
4+
// default;
5+
// case 42;
6+
7+
// === Details ===
8+
[
9+
{
10+
"kind": "property",
11+
"name": "case",
12+
"containerName": "__object",
13+
"isLocal": true,
14+
"isAmbient": false,
15+
"unverified": false,
16+
"failedAliasResolution": false
17+
}
18+
]
19+
20+
21+
22+
// === goToDefinition ===
23+
// === /tests/cases/fourslash/goToDefinitionSwitchCase6.ts ===
24+
// [|export default { case };
25+
// /*GOTO DEF*/default;
26+
// case 42;|]
27+
28+
// === Details ===
29+
[
30+
{
31+
"kind": "module",
32+
"name": "\"/tests/cases/fourslash/goToDefinitionSwitchCase6\"",
33+
"containerName": "",
34+
"isLocal": false,
35+
"isAmbient": false,
36+
"unverified": false,
37+
"failedAliasResolution": false
38+
}
39+
]
40+
41+
42+
43+
// === goToDefinition ===
44+
// === /tests/cases/fourslash/goToDefinitionSwitchCase6.ts ===
45+
// export default { case };
46+
// default;
47+
// /*GOTO DEF*/case 42;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionSwitchCase7.ts ===
3+
// switch (null) {
4+
// case null:
5+
// [|export /*GOTO DEF*/default 123;|]
6+
7+
// === Details ===
8+
[
9+
{
10+
"kind": "var",
11+
"name": "default",
12+
"containerName": "",
13+
"isLocal": true,
14+
"isAmbient": false,
15+
"unverified": false,
16+
"failedAliasResolution": false
17+
}
18+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////switch (null ) {
4+
//// [|/*start*/case|] null: break;
5+
////}
6+
7+
verify.baselineGoToDefinition("start");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////switch (null) {
4+
//// [|/*start*/default|]: break;
5+
////}
6+
7+
verify.baselineGoToDefinition("start");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////switch (null) {
4+
//// [|/*start1*/default|]: {
5+
//// switch (null) {
6+
//// [|/*start2*/default|]: break;
7+
//// }
8+
//// };
9+
////}
10+
11+
verify.baselineGoToDefinition("start1", "start2");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// switch (null) {
4+
//// case null: break;
5+
//// }
6+
////
7+
//// switch (null) {
8+
//// [|/*start*/case|] null: break;
9+
//// }
10+
11+
verify.baselineGoToDefinition("start");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////export [|/*start*/default|] {}
4+
5+
verify.baselineGoToDefinition("start");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////export default { [|/*a*/case|] };
4+
////[|/*b*/default|];
5+
////[|/*c*/case|] 42;
6+
7+
verify.baselineGoToDefinition("a", "b", "c");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////switch (null) {
4+
//// case null:
5+
//// export [|/*start*/default|] 123;
6+
7+
verify.baselineGoToDefinition("start");

0 commit comments

Comments
 (0)