mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
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>
This commit is contained in:
parent
9a0c7b1c3b
commit
1717826b6e
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
@ -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;
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
7
tests/cases/fourslash/goToDefinitionSwitchCase1.ts
Normal file
7
tests/cases/fourslash/goToDefinitionSwitchCase1.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////switch (null ) {
|
||||
//// [|/*start*/case|] null: break;
|
||||
////}
|
||||
|
||||
verify.baselineGoToDefinition("start");
|
||||
7
tests/cases/fourslash/goToDefinitionSwitchCase2.ts
Normal file
7
tests/cases/fourslash/goToDefinitionSwitchCase2.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////switch (null) {
|
||||
//// [|/*start*/default|]: break;
|
||||
////}
|
||||
|
||||
verify.baselineGoToDefinition("start");
|
||||
11
tests/cases/fourslash/goToDefinitionSwitchCase3.ts
Normal file
11
tests/cases/fourslash/goToDefinitionSwitchCase3.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////switch (null) {
|
||||
//// [|/*start1*/default|]: {
|
||||
//// switch (null) {
|
||||
//// [|/*start2*/default|]: break;
|
||||
//// }
|
||||
//// };
|
||||
////}
|
||||
|
||||
verify.baselineGoToDefinition("start1", "start2");
|
||||
11
tests/cases/fourslash/goToDefinitionSwitchCase4.ts
Normal file
11
tests/cases/fourslash/goToDefinitionSwitchCase4.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// switch (null) {
|
||||
//// case null: break;
|
||||
//// }
|
||||
////
|
||||
//// switch (null) {
|
||||
//// [|/*start*/case|] null: break;
|
||||
//// }
|
||||
|
||||
verify.baselineGoToDefinition("start");
|
||||
5
tests/cases/fourslash/goToDefinitionSwitchCase5.ts
Normal file
5
tests/cases/fourslash/goToDefinitionSwitchCase5.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////export [|/*start*/default|] {}
|
||||
|
||||
verify.baselineGoToDefinition("start");
|
||||
7
tests/cases/fourslash/goToDefinitionSwitchCase6.ts
Normal file
7
tests/cases/fourslash/goToDefinitionSwitchCase6.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////export default { [|/*a*/case|] };
|
||||
////[|/*b*/default|];
|
||||
////[|/*c*/case|] 42;
|
||||
|
||||
verify.baselineGoToDefinition("a", "b", "c");
|
||||
7
tests/cases/fourslash/goToDefinitionSwitchCase7.ts
Normal file
7
tests/cases/fourslash/goToDefinitionSwitchCase7.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////switch (null) {
|
||||
//// case null:
|
||||
//// export [|/*start*/default|] 123;
|
||||
|
||||
verify.baselineGoToDefinition("start");
|
||||
Loading…
x
Reference in New Issue
Block a user