Skip to content

Commit 2ba0e47

Browse files
jakebaileyCopilot
andauthored
Add docs to signature help (#2009)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ae5d110 commit 2ba0e47

20 files changed

+1038
-49
lines changed

internal/fourslash/fourslash.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,18 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr
297297
if capabilitiesWithDefaults.TextDocument.Hover == nil {
298298
capabilitiesWithDefaults.TextDocument.Hover = defaultHoverCapabilities
299299
}
300+
if capabilitiesWithDefaults.TextDocument.SignatureHelp == nil {
301+
capabilitiesWithDefaults.TextDocument.SignatureHelp = &lsproto.SignatureHelpClientCapabilities{
302+
SignatureInformation: &lsproto.ClientSignatureInformationOptions{
303+
DocumentationFormat: &[]lsproto.MarkupKind{lsproto.MarkupKindMarkdown, lsproto.MarkupKindPlainText},
304+
ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{
305+
LabelOffsetSupport: ptrTrue,
306+
},
307+
ActiveParameterSupport: ptrTrue,
308+
},
309+
ContextSupport: ptrTrue,
310+
}
311+
}
300312
return &capabilitiesWithDefaults
301313
}
302314

internal/ls/hover.go

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -59,59 +59,64 @@ func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Check
5959
if quickInfo == "" {
6060
return "", ""
6161
}
62+
return quickInfo, l.getDocumentationFromDeclaration(c, declaration, contentFormat)
63+
}
64+
65+
func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, declaration *ast.Node, contentFormat lsproto.MarkupKind) string {
66+
if declaration == nil {
67+
return ""
68+
}
6269
isMarkdown := contentFormat == lsproto.MarkupKindMarkdown
6370
var b strings.Builder
64-
if declaration != nil {
65-
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
66-
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
67-
if jsdoc.Kind == ast.KindJSDoc {
68-
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
69-
for _, tag := range tags.Nodes {
70-
if tag.Kind == ast.KindJSDocTypeTag {
71-
continue
72-
}
73-
b.WriteString("\n\n")
74-
if isMarkdown {
75-
b.WriteString("*@")
76-
b.WriteString(tag.TagName().Text())
77-
b.WriteString("*")
78-
} else {
79-
b.WriteString("@")
80-
b.WriteString(tag.TagName().Text())
81-
}
82-
switch tag.Kind {
83-
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
84-
writeOptionalEntityName(&b, tag.Name())
85-
case ast.KindJSDocAugmentsTag:
86-
writeOptionalEntityName(&b, tag.AsJSDocAugmentsTag().ClassName)
87-
case ast.KindJSDocSeeTag:
88-
writeOptionalEntityName(&b, tag.AsJSDocSeeTag().NameExpression)
89-
case ast.KindJSDocTemplateTag:
90-
for i, tp := range tag.TypeParameters() {
91-
if i != 0 {
92-
b.WriteString(",")
93-
}
94-
writeOptionalEntityName(&b, tp.Name())
71+
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
72+
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
73+
if jsdoc.Kind == ast.KindJSDoc {
74+
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
75+
for _, tag := range tags.Nodes {
76+
if tag.Kind == ast.KindJSDocTypeTag {
77+
continue
78+
}
79+
b.WriteString("\n\n")
80+
if isMarkdown {
81+
b.WriteString("*@")
82+
b.WriteString(tag.TagName().Text())
83+
b.WriteString("*")
84+
} else {
85+
b.WriteString("@")
86+
b.WriteString(tag.TagName().Text())
87+
}
88+
switch tag.Kind {
89+
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
90+
writeOptionalEntityName(&b, tag.Name())
91+
case ast.KindJSDocAugmentsTag:
92+
writeOptionalEntityName(&b, tag.AsJSDocAugmentsTag().ClassName)
93+
case ast.KindJSDocSeeTag:
94+
writeOptionalEntityName(&b, tag.AsJSDocSeeTag().NameExpression)
95+
case ast.KindJSDocTemplateTag:
96+
for i, tp := range tag.TypeParameters() {
97+
if i != 0 {
98+
b.WriteString(",")
9599
}
100+
writeOptionalEntityName(&b, tp.Name())
96101
}
97-
comments := tag.Comments()
98-
if len(comments) != 0 {
99-
if commentHasPrefix(comments, "```") {
100-
b.WriteString("\n")
101-
} else {
102-
b.WriteString(" ")
103-
if !commentHasPrefix(comments, "-") {
104-
b.WriteString("— ")
105-
}
102+
}
103+
comments := tag.Comments()
104+
if len(comments) != 0 {
105+
if commentHasPrefix(comments, "```") {
106+
b.WriteString("\n")
107+
} else {
108+
b.WriteString(" ")
109+
if !commentHasPrefix(comments, "-") {
110+
b.WriteString("— ")
106111
}
107-
l.writeComments(&b, c, comments, isMarkdown)
108112
}
113+
l.writeComments(&b, c, comments, isMarkdown)
109114
}
110115
}
111116
}
112117
}
113118
}
114-
return quickInfo, b.String()
119+
return b.String()
115120
}
116121

117122
func formatQuickInfo(quickInfo string) string {

internal/ls/signaturehelp.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func (l *LanguageService) ProvideSignatureHelp(
4444
position lsproto.Position,
4545
context *lsproto.SignatureHelpContext,
4646
clientOptions *lsproto.SignatureHelpClientCapabilities,
47+
docFormat lsproto.MarkupKind,
4748
) (lsproto.SignatureHelpResponse, error) {
4849
program, sourceFile := l.getProgramAndFile(documentURI)
4950
items := l.GetSignatureHelpItems(
@@ -52,7 +53,8 @@ func (l *LanguageService) ProvideSignatureHelp(
5253
program,
5354
sourceFile,
5455
context,
55-
clientOptions)
56+
clientOptions,
57+
docFormat)
5658
return lsproto.SignatureHelpOrNull{SignatureHelp: items}, nil
5759
}
5860

@@ -63,6 +65,7 @@ func (l *LanguageService) GetSignatureHelpItems(
6365
sourceFile *ast.SourceFile,
6466
context *lsproto.SignatureHelpContext,
6567
clientOptions *lsproto.SignatureHelpClientCapabilities,
68+
docFormat lsproto.MarkupKind,
6669
) *lsproto.SignatureHelp {
6770
typeChecker, done := program.GetTypeCheckerForFile(ctx, sourceFile)
6871
defer done()
@@ -140,7 +143,7 @@ func (l *LanguageService) GetSignatureHelpItems(
140143

141144
// return typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
142145
if candidateInfo.candidateInfo != nil {
143-
return createSignatureHelpItems(candidateInfo.candidateInfo.candidates, candidateInfo.candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker, onlyUseSyntacticOwners, clientOptions)
146+
return l.createSignatureHelpItems(candidateInfo.candidateInfo.candidates, candidateInfo.candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker, onlyUseSyntacticOwners, clientOptions, docFormat)
144147
}
145148
return createTypeHelpItems(candidateInfo.typeInfo, argumentInfo, sourceFile, clientOptions, typeChecker)
146149
}
@@ -202,7 +205,7 @@ func getTypeHelpItem(symbol *ast.Symbol, typeParameter []*checker.Type, enclosin
202205
}
203206
}
204207

205-
func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature *checker.Signature, argumentInfo *argumentListInfo, sourceFile *ast.SourceFile, c *checker.Checker, useFullPrefix bool, clientOptions *lsproto.SignatureHelpClientCapabilities) *lsproto.SignatureHelp {
208+
func (l *LanguageService) createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature *checker.Signature, argumentInfo *argumentListInfo, sourceFile *ast.SourceFile, c *checker.Checker, useFullPrefix bool, clientOptions *lsproto.SignatureHelpClientCapabilities, docFormat lsproto.MarkupKind) *lsproto.SignatureHelp {
206209
enclosingDeclaration := getEnclosingDeclarationFromInvocation(argumentInfo.invocation)
207210
if enclosingDeclaration == nil {
208211
return nil
@@ -223,7 +226,7 @@ func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature
223226
}
224227
items := make([][]signatureInformation, len(candidates))
225228
for i, candidateSignature := range candidates {
226-
items[i] = getSignatureHelpItem(candidateSignature, argumentInfo.isTypeParameterList, callTargetDisplayParts.String(), enclosingDeclaration, sourceFile, c)
229+
items[i] = l.getSignatureHelpItem(candidateSignature, argumentInfo.isTypeParameterList, callTargetDisplayParts.String(), enclosingDeclaration, sourceFile, c, docFormat)
227230
}
228231

229232
selectedItemIndex := 0
@@ -262,9 +265,18 @@ func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature
262265
for j, param := range item.Parameters {
263266
parameters[j] = param.parameterInfo
264267
}
268+
var documentation *lsproto.StringOrMarkupContent
269+
if item.Documentation != nil {
270+
documentation = &lsproto.StringOrMarkupContent{
271+
MarkupContent: &lsproto.MarkupContent{
272+
Kind: docFormat,
273+
Value: *item.Documentation,
274+
},
275+
}
276+
}
265277
signatureInformation[i] = &lsproto.SignatureInformation{
266278
Label: item.Label,
267-
Documentation: nil,
279+
Documentation: documentation,
268280
Parameters: &parameters,
269281
}
270282
}
@@ -291,7 +303,7 @@ func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature
291303
return help
292304
}
293305

294-
func getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool, callTargetSymbol string, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker) []signatureInformation {
306+
func (l *LanguageService) getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool, callTargetSymbol string, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, c *checker.Checker, docFormat lsproto.MarkupKind) []signatureInformation {
295307
var infos []*signatureHelpItemInfo
296308
if isTypeParameterList {
297309
infos = itemInfoForTypeParameters(candidate, c, enclosingDeclaration, sourceFile)
@@ -301,6 +313,15 @@ func getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool
301313

302314
suffixDisplayParts := returnTypeToDisplayParts(candidate, c)
303315

316+
// Generate documentation from the signature's declaration
317+
var documentation *string
318+
if declaration := candidate.Declaration(); declaration != nil {
319+
doc := l.getDocumentationFromDeclaration(c, declaration, docFormat)
320+
if doc != "" {
321+
documentation = &doc
322+
}
323+
}
324+
304325
result := make([]signatureInformation, len(infos))
305326
for i, info := range infos {
306327
var display strings.Builder
@@ -309,7 +330,7 @@ func getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool
309330
display.WriteString(suffixDisplayParts)
310331
result[i] = signatureInformation{
311332
Label: display.String(),
312-
Documentation: nil,
333+
Documentation: documentation,
313334
Parameters: info.parameters,
314335
IsVariadic: info.isVariadic,
315336
}

internal/lsp/server.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,7 @@ func (s *Server) handleSignatureHelp(ctx context.Context, languageService *ls.La
818818
params.Position,
819819
params.Context,
820820
s.initializeParams.Capabilities.TextDocument.SignatureHelp,
821+
getSignatureHelpDocumentationFormat(s.initializeParams),
821822
)
822823
}
823824

@@ -1021,3 +1022,18 @@ func getHoverContentFormat(params *lsproto.InitializeParams) lsproto.MarkupKind
10211022
// Return the first (most preferred) format
10221023
return formats[0]
10231024
}
1025+
1026+
func getSignatureHelpDocumentationFormat(params *lsproto.InitializeParams) lsproto.MarkupKind {
1027+
if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil || params.Capabilities.TextDocument.SignatureHelp == nil ||
1028+
params.Capabilities.TextDocument.SignatureHelp.SignatureInformation == nil ||
1029+
params.Capabilities.TextDocument.SignatureHelp.SignatureInformation.DocumentationFormat == nil {
1030+
// Default to plaintext if no preference specified
1031+
return lsproto.MarkupKindPlainText
1032+
}
1033+
formats := *params.Capabilities.TextDocument.SignatureHelp.SignatureInformation.DocumentationFormat
1034+
if len(formats) == 0 {
1035+
return lsproto.MarkupKindPlainText
1036+
}
1037+
// Return the first (most preferred) format
1038+
return formats[0]
1039+
}

testdata/baselines/reference/fourslash/signatureHelp/jsDocDontBreakWithNamespaces.baseline

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
// ^
99
// | ----------------------------------------------------------------------
1010
// | foo(): module
11+
// |
12+
// |
13+
// | *@returns* — :@nodefuel/web~Webserver~wsServer#hello} Websocket server object
14+
// |
1115
// | ----------------------------------------------------------------------
1216
//
1317
// /**
@@ -42,6 +46,10 @@
4246
"signatures": [
4347
{
4448
"label": "foo(): module",
49+
"documentation": {
50+
"kind": "markdown",
51+
"value": "\n\n*@returns* — :@nodefuel/web~Webserver~wsServer#hello} Websocket server object\n"
52+
},
4553
"parameters": []
4654
}
4755
],

testdata/baselines/reference/fourslash/signatureHelp/jsDocFunctionSignatures5.baseline

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@
1515
// ^
1616
// | ----------------------------------------------------------------------
1717
// | pathFilter(**basePath: String**, pattern: String, type: String, options: Object): any[]
18+
// | Filters a path based on a regexp or glob pattern.
19+
// |
20+
// | *@param* `basePath` — The base path where the search will be performed.
21+
// |
22+
// |
23+
// | *@param* `pattern` — A string defining a regexp of a glob pattern.
24+
// |
25+
// |
26+
// | *@param* `type` — The search pattern type, can be a regexp or a glob.
27+
// |
28+
// |
29+
// | *@param* `options` — A object containing options to the search.
30+
// |
31+
// |
32+
// | *@return* — A list containing the filtered paths.
33+
// |
1834
// | ----------------------------------------------------------------------
1935
[
2036
{
@@ -31,6 +47,10 @@
3147
"signatures": [
3248
{
3349
"label": "pathFilter(basePath: String, pattern: String, type: String, options: Object): any[]",
50+
"documentation": {
51+
"kind": "markdown",
52+
"value": "Filters a path based on a regexp or glob pattern.\n\n*@param* `basePath` — The base path where the search will be performed.\n\n\n*@param* `pattern` — A string defining a regexp of a glob pattern.\n\n\n*@param* `type` — The search pattern type, can be a regexp or a glob.\n\n\n*@param* `options` — A object containing options to the search.\n\n\n*@return* — A list containing the filtered paths.\n"
53+
},
3454
"parameters": [
3555
{
3656
"label": "basePath: String"

0 commit comments

Comments
 (0)