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");