Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/services/goToDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,14 +653,43 @@ function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node:
}
}

/**
* Gets the ScriptElementKind for a specific declaration.
* For merged declarations, this ensures each declaration returns its own kind
* rather than using the combined symbol flags.
*
* Fixes #22467: getDefinitionAtPosition doesn't distinguish different kinds in a merged declaration
*/
function getKindFromDeclaration(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node): ScriptElementKind {
// Check the specific declaration's syntax kind first
// This handles merged declarations correctly (e.g., namespace A + class A + interface A)
switch (declaration.kind) {
case SyntaxKind.ClassDeclaration:
return ScriptElementKind.classElement;
case SyntaxKind.ClassExpression:
return ScriptElementKind.localClassElement;
case SyntaxKind.InterfaceDeclaration:
return ScriptElementKind.interfaceElement;
case SyntaxKind.ModuleDeclaration:
return ScriptElementKind.moduleElement;
case SyntaxKind.EnumDeclaration:
return ScriptElementKind.enumElement;
case SyntaxKind.TypeAliasDeclaration:
return ScriptElementKind.typeElement;
// For other declaration types, fall back to the existing symbol-based logic
default:
return SymbolDisplay.getSymbolKind(checker, symbol, node);
}
}

/**
* Creates a DefinitionInfo from a Declaration, using the declaration's name if possible.
*
* @internal
*/
export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, unverified?: boolean, failedAliasResolution?: boolean): DefinitionInfo {
const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node);
const symbolKind = getKindFromDeclaration(declaration, checker, symbol, node);
const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : "";
return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, unverified, failedAliasResolution);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// === goToDefinition ===
// === /tests/cases/fourslash/test.ts ===
// <|namespace [|{| defId: 0 |}A|] {
// export interface B {}
// }|>
// <|class [|{| defId: 1 |}A|] {}|>
// <|interface [|{| defId: 2 |}A|] {}|>
// var x: /*GOTO DEF*/A;

// === Details ===
[
{
"defId": 0,
"kind": "module",
"name": "A",
"containerName": "",
"isLocal": false,
"isAmbient": false,
"unverified": false,
"failedAliasResolution": false
},
{
"defId": 1,
"kind": "class",
"name": "A",
"containerName": "",
"isLocal": false,
"isAmbient": false,
"unverified": false,
"failedAliasResolution": false
},
{
"defId": 2,
"kind": "interface",
"name": "A",
"containerName": "",
"isLocal": false,
"isAmbient": false,
"unverified": false,
"failedAliasResolution": false
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
// === Details ===
[
{
"kind": "parameter",
"kind": "class",
"name": "C",
"containerName": "",
"isLocal": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
// === Details ===
[
{
"kind": "parameter",
"kind": "class",
"name": "C",
"containerName": "",
"isLocal": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
[
{
"defId": 0,
"kind": "const",
"kind": "type",
"name": "T",
"containerName": "\"/tests/cases/fourslash/foo\"",
"isLocal": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@
},
{
"defId": 1,
"kind": "var",
"kind": "interface",
"name": "Array",
"containerName": "",
"isLocal": false,
Expand Down Expand Up @@ -486,7 +486,7 @@
},
{
"defId": 1,
"kind": "var",
"kind": "interface",
"name": "Array",
"containerName": "",
"isLocal": false,
Expand Down
15 changes: 15 additions & 0 deletions tests/cases/fourslash/goToDefinitionMergedDeclarations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path="fourslash.ts" />

// @Filename: test.ts
/////*namespaceDecl*/namespace A {
//// export interface B {}
////}
/////*classDecl*/class A {}
/////*interfaceDecl*/interface A {}
////var x: /*usage*/A;

// Test that getDefinitionAtPosition returns the correct kind for each merged declaration
// Issue #22467: Before fix, all three return 'class' (bug)
// After fix: Returns 'module', 'class', 'interface' respectively

verify.baselineGoToDefinition("usage");