Skip to content
Closed
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
8 changes: 7 additions & 1 deletion internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,13 @@ func (f *FourslashTest) VerifyBaselineGoToDefinition(
} else if result.Location != nil {
resultAsLocations = []lsproto.Location{*result.Location}
} else if result.DefinitionLinks != nil {
t.Fatalf("Unexpected definition response type at marker '%s': %T", *f.lastKnownMarkerName, result.DefinitionLinks)
// For DefinitionLinks, extract the target locations
resultAsLocations = core.Map(*result.DefinitionLinks, func(link *lsproto.LocationLink) lsproto.Location {
return lsproto.Location{
Uri: link.TargetUri,
Range: link.TargetSelectionRange,
}
})
}

f.addResultToBaseline(t, "goToDefinition", f.getBaselineForLocationsWithFileContents(resultAsLocations, baselineFourslashLocationsOptions{
Expand Down
55 changes: 50 additions & 5 deletions internal/ls/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/microsoft/typescript-go/internal/scanner"
)

func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (lsproto.DefinitionResponse, error) {
func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position, clientCapabilities *lsproto.DefinitionClientCapabilities) (lsproto.DefinitionResponse, error) {
program, file := l.getProgramAndFile(documentURI)
node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position)))
if node.Kind == ast.KindSourceFile {
Expand All @@ -24,13 +24,13 @@ func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsp

if node.Kind == ast.KindOverrideKeyword {
if sym := getSymbolForOverriddenMember(c, node); sym != nil {
return l.createLocationsFromDeclarations(sym.Declarations), nil
return l.createDefinitionResponse(sym.Declarations, node, file, clientCapabilities), nil
}
}

if ast.IsJumpStatementTarget(node) {
if label := getTargetLabel(node.Parent, node.Text()); label != nil {
return l.createLocationsFromDeclarations([]*ast.Node{label}), nil
return l.createDefinitionResponse([]*ast.Node{label}, node, file, clientCapabilities), nil
}
}

Expand All @@ -43,7 +43,7 @@ func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsp

if node.Kind == ast.KindReturnKeyword || node.Kind == ast.KindYieldKeyword || node.Kind == ast.KindAwaitKeyword {
if fn := ast.FindAncestor(node, ast.IsFunctionLikeDeclaration); fn != nil {
return l.createLocationsFromDeclarations([]*ast.Node{fn}), nil
return l.createDefinitionResponse([]*ast.Node{fn}, node, file, clientCapabilities), nil
}
}

Expand All @@ -54,7 +54,7 @@ func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsp
nonFunctionDeclarations := core.Filter(slices.Clip(declarations), func(node *ast.Node) bool { return !ast.IsFunctionLike(node) })
declarations = append(nonFunctionDeclarations, calledDeclaration)
}
return l.createLocationsFromDeclarations(declarations), nil
return l.createDefinitionResponse(declarations, node, file, clientCapabilities), nil
}

func (l *LanguageService) ProvideTypeDefinition(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (lsproto.DefinitionResponse, error) {
Expand Down Expand Up @@ -99,6 +99,51 @@ func getDeclarationNameForKeyword(node *ast.Node) *ast.Node {
return node
}

func (l *LanguageService) createDefinitionResponse(declarations []*ast.Node, originNode *ast.Node, originFile *ast.SourceFile, clientCapabilities *lsproto.DefinitionClientCapabilities) lsproto.DefinitionResponse {
// Check if client supports LocationLink
if clientCapabilities != nil && clientCapabilities.LinkSupport != nil && *clientCapabilities.LinkSupport {
return l.createLocationLinksFromDeclarations(declarations, originNode, originFile)
}
// Fall back to traditional Location response
return l.createLocationsFromDeclarations(declarations)
}

func (l *LanguageService) createLocationLinksFromDeclarations(declarations []*ast.Node, originNode *ast.Node, originFile *ast.SourceFile) lsproto.DefinitionResponse {
someHaveBody := core.Some(declarations, func(node *ast.Node) bool { return node.Body() != nil })
links := make([]*lsproto.LocationLink, 0, len(declarations))

// Calculate origin selection range (the "bound span")
originSelectionRange := l.createLspRangeFromNode(originNode, originFile)

for _, decl := range declarations {
if !someHaveBody || decl.Body() != nil {
file := ast.GetSourceFileOfNode(decl)
name := core.OrElse(ast.GetNameOfDeclaration(decl), decl)

// For targetRange, use the full declaration range
var targetRange *lsproto.Range
if decl.Body() != nil {
// For declarations with body, include the full declaration
targetRange = l.createLspRangeFromBounds(scanner.GetTokenPosOfNode(decl, file, false), decl.End(), file)
} else {
// For declarations without body, use the declaration itself
targetRange = l.createLspRangeFromNode(decl, file)
}

// For targetSelectionRange, use just the name/identifier part
targetSelectionRange := l.createLspRangeFromNode(name, file)

links = append(links, &lsproto.LocationLink{
OriginSelectionRange: originSelectionRange,
TargetUri: FileNameToDocumentURI(file.FileName()),
TargetRange: *targetRange,
TargetSelectionRange: *targetSelectionRange,
})
}
}
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{DefinitionLinks: &links}
}

func (l *LanguageService) createLocationsFromDeclarations(declarations []*ast.Node) lsproto.DefinitionResponse {
locations := make([]lsproto.Location, 0, len(declarations))
for _, decl := range declarations {
Expand Down
29 changes: 28 additions & 1 deletion internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ func (s *Server) handleSignatureHelp(ctx context.Context, languageService *ls.La
}

func (s *Server) handleDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.DefinitionParams) (lsproto.DefinitionResponse, error) {
return ls.ProvideDefinition(ctx, params.TextDocument.Uri, params.Position)
return ls.ProvideDefinition(ctx, params.TextDocument.Uri, params.Position, definitionCapabilities(s.initializeParams))
}

func (s *Server) handleTypeDefinition(ctx context.Context, ls *ls.LanguageService, params *lsproto.TypeDefinitionParams) (lsproto.TypeDefinitionResponse, error) {
Expand Down Expand Up @@ -970,3 +970,30 @@ func getCompletionClientCapabilities(params *lsproto.InitializeParams) *lsproto.
}
return params.Capabilities.TextDocument.Completion
}

func definitionCapabilities(params *lsproto.InitializeParams) *lsproto.DefinitionClientCapabilities {
if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil {
// Return default capabilities with LinkSupport enabled
return &lsproto.DefinitionClientCapabilities{
LinkSupport: ptrTo(true),
}
}

capabilities := params.Capabilities.TextDocument.Definition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call this definitionCapabilities because it looks like it's checked above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to definitionCapabilities. Commit a8ba72c.

if capabilities == nil {
// Return default capabilities with LinkSupport enabled
return &lsproto.DefinitionClientCapabilities{
LinkSupport: ptrTo(true),
}
}

// If capabilities exist but LinkSupport is not specified, default to true
if capabilities.LinkSupport == nil {
// Copy existing capabilities and override LinkSupport
result := *capabilities
result.LinkSupport = ptrTo(true)
return &result
}

return capabilities
}
2 changes: 1 addition & 1 deletion internal/project/untitled_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ x++;`
assert.Assert(t, len(refs) == 3, "Expected 3 references, got %d", len(refs))

// Also test definition using ProvideDefinition
definition, err := languageService.ProvideDefinition(ctx, uri, lspPosition)
definition, err := languageService.ProvideDefinition(ctx, uri, lspPosition, nil)
assert.NilError(t, err)
if definition.Locations != nil {
t.Logf("Definition found: %d locations", len(*definition.Locations))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// === goToDefinition ===
// === /index.ts ===
// export class Foo {
// === /indexdef.d.ts ===
// export declare class Foo {
// member: string;
// [|methodName|](propName: SomeType): void {}
// otherMethod() {
// if (Math.random() > 0.5) {
// return {x: 42};
// [|methodName|](propName: SomeType): void;
// otherMethod(): {
// x: number;
// y?: undefined;
// // --- (line: 7) skipped ---

// === /mymodule.ts ===
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// === goToDefinition ===
// === /index.ts ===
// export class Foo {
// === /out/indexdef.d.ts ===
// export declare class Foo {
// member: string;
// [|methodName|](propName: SomeType): void {}
// otherMethod() {
// if (Math.random() > 0.5) {
// return {x: 42};
// [|methodName|](propName: SomeType): void;
// otherMethod(): {
// x: number;
// y?: undefined;
// // --- (line: 7) skipped ---

// === /mymodule.ts ===
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// === goToDefinition ===
// === /BaseClass/Source.ts ===
// class [|Control|]{
// constructor(){
// return;
// }
// // --- (line: 5) skipped ---
// === /BaseClass/Source.d.ts ===
// declare class [|Control|] {
// constructor();
// /** this is a super var */
// myVar: boolean | 'yeah';
// }
// //# sourceMappingURL=Source.d.ts.map

// === /buttonClass/Source.ts ===
// // I cannot F12 navigate to Control
Expand All @@ -18,14 +19,13 @@


// === goToDefinition ===
// === /BaseClass/Source.ts ===
// class Control{
// constructor(){
// return;
// }
// === /BaseClass/Source.d.ts ===
// declare class Control {
// constructor();
// /** this is a super var */
// public [|myVar|]: boolean | 'yeah' = true;
// [|myVar|]: boolean | 'yeah';
// }
// //# sourceMappingURL=Source.d.ts.map

// === /buttonClass/Source.ts ===
// --- (line: 3) skipped ---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// === goToDefinition ===
// === /home/src/workspaces/project/node_modules/a/src/index.ts ===
// export class [|Foo|] {
// === /home/src/workspaces/project/node_modules/a/dist/index.d.ts ===
// export declare class [|Foo|] {
// bar: any;
// }
//
// //# sourceMappingURL=index.d.ts.map

// === /home/src/workspaces/project/index.ts ===
// import { Foo/*GOTO DEF*/ } from "a";
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
// [|constructor(protected readonly cArg: string) {}|]
// }
//
// export class [|Derived|] extends Base {
// readonly email = this.cArg.getByLabel('Email')
// readonly password = this.cArg.getByLabel('Password')
// }
// export class Derived extends Base {
// // --- (line: 6) skipped ---

// === /main.ts ===
// import { Derived } from './definitions'
Expand All @@ -16,14 +14,6 @@


// === goToDefinition ===
// === /defInSameFile.ts ===
// import { Base } from './definitions'
// class [|SameFile|] extends Base {
// readonly name: string = 'SameFile'
// }
// const SameFile = new /*GOTO DEF*/SameFile(cArg)
// const wrapper = new Base(cArg)

// === /definitions.ts ===
// export class Base {
// [|constructor(protected readonly cArg: string) {}|]
Expand All @@ -32,12 +22,20 @@
// export class Derived extends Base {
// // --- (line: 6) skipped ---

// === /defInSameFile.ts ===
// import { Base } from './definitions'
// class SameFile extends Base {
// readonly name: string = 'SameFile'
// }
// const SameFile = new /*GOTO DEF*/SameFile(cArg)
// const wrapper = new Base(cArg)



// === goToDefinition ===
// === /hasConstructor.ts ===
// import { Base } from './definitions'
// class [|HasConstructor|] extends Base {
// class HasConstructor extends Base {
// [|constructor() {}|]
// readonly name: string = '';
// }
Expand All @@ -47,7 +45,7 @@

// === goToDefinition ===
// === /definitions.ts ===
// export class [|Base|] {
// export class Base {
// [|constructor(protected readonly cArg: string) {}|]
// }
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// === goToDefinition ===
// === /goToDefinitionConstructorOfClassExpression01.ts ===
// var x = class [|C|] {
// var x = class C {
// [|constructor() {
// var other = new /*GOTO DEF*/C;
// }|]
Expand All @@ -13,11 +13,10 @@

// === goToDefinition ===
// === /goToDefinitionConstructorOfClassExpression01.ts ===
// --- (line: 3) skipped ---
// }
// --- (line: 4) skipped ---
// }
//
// var y = class [|C|] extends x {
// var y = class C extends x {
// [|constructor() {
// super();
// var other = new /*GOTO DEF*/C;
Expand All @@ -38,12 +37,11 @@
// }
//
// var y = class C extends x {
// constructor() {
// super();
// var other = new C;
// }
// // --- (line: 8) skipped ---

// --- (line: 11) skipped ---
// }
// var z = class [|C|] extends x {
// var z = class C extends x {
// m() {
// return new /*GOTO DEF*/C;
// }
Expand All @@ -68,7 +66,7 @@

// === goToDefinition ===
// === /goToDefinitionConstructorOfClassExpression01.ts ===
// var [|x|] = class C {
// var x = class C {
// [|constructor() {
// var other = new C;
// }|]
Expand All @@ -89,11 +87,10 @@

// === goToDefinition ===
// === /goToDefinitionConstructorOfClassExpression01.ts ===
// --- (line: 3) skipped ---
// }
// --- (line: 4) skipped ---
// }
//
// var [|y|] = class C extends x {
// var y = class C extends x {
// [|constructor() {
// super();
// var other = new C;
Expand Down Expand Up @@ -121,17 +118,9 @@
// }
//
// var y = class C extends x {
// constructor() {
// super();
// var other = new C;
// }
// }
// var [|z|] = class C extends x {
// m() {
// return new C;
// }
// }
//
// // --- (line: 8) skipped ---

// --- (line: 18) skipped ---
// var x1 = new C();
// var x2 = new x();
// var y1 = new y();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// export var x;
// }
//
// class [|Foo|] {
// class Foo {
// [|constructor() {
// }|]
// }
Expand Down
Loading