Skip to content
Merged
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
12 changes: 12 additions & 0 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr
if capabilitiesWithDefaults.TextDocument.Hover == nil {
capabilitiesWithDefaults.TextDocument.Hover = defaultHoverCapabilities
}
if capabilitiesWithDefaults.TextDocument.SignatureHelp == nil {
capabilitiesWithDefaults.TextDocument.SignatureHelp = &lsproto.SignatureHelpClientCapabilities{
SignatureInformation: &lsproto.ClientSignatureInformationOptions{
DocumentationFormat: &[]lsproto.MarkupKind{lsproto.MarkupKindMarkdown, lsproto.MarkupKindPlainText},
ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{
LabelOffsetSupport: ptrTrue,
},
ActiveParameterSupport: ptrTrue,
},
ContextSupport: ptrTrue,
Comment on lines +304 to +309
Copy link
Member Author

@jakebailey jakebailey Nov 3, 2025

Choose a reason for hiding this comment

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

We are not actually checking these correctly currently (different PR).

}
}
return &capabilitiesWithDefaults
}

Expand Down
89 changes: 47 additions & 42 deletions internal/ls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,59 +59,64 @@ func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Check
if quickInfo == "" {
return "", ""
}
return quickInfo, l.getDocumentationFromDeclaration(c, declaration, contentFormat)
}

func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, declaration *ast.Node, contentFormat lsproto.MarkupKind) string {
if declaration == nil {
return ""
}
isMarkdown := contentFormat == lsproto.MarkupKindMarkdown
var b strings.Builder
if declaration != nil {
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
if jsdoc.Kind == ast.KindJSDoc {
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if tag.Kind == ast.KindJSDocTypeTag {
continue
}
b.WriteString("\n\n")
if isMarkdown {
b.WriteString("*@")
b.WriteString(tag.TagName().Text())
b.WriteString("*")
} else {
b.WriteString("@")
b.WriteString(tag.TagName().Text())
}
switch tag.Kind {
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
writeOptionalEntityName(&b, tag.Name())
case ast.KindJSDocAugmentsTag:
writeOptionalEntityName(&b, tag.AsJSDocAugmentsTag().ClassName)
case ast.KindJSDocSeeTag:
writeOptionalEntityName(&b, tag.AsJSDocSeeTag().NameExpression)
case ast.KindJSDocTemplateTag:
for i, tp := range tag.TypeParameters() {
if i != 0 {
b.WriteString(",")
}
writeOptionalEntityName(&b, tp.Name())
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
if jsdoc.Kind == ast.KindJSDoc {
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if tag.Kind == ast.KindJSDocTypeTag {
continue
}
b.WriteString("\n\n")
if isMarkdown {
b.WriteString("*@")
b.WriteString(tag.TagName().Text())
b.WriteString("*")
} else {
b.WriteString("@")
b.WriteString(tag.TagName().Text())
}
switch tag.Kind {
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
writeOptionalEntityName(&b, tag.Name())
case ast.KindJSDocAugmentsTag:
writeOptionalEntityName(&b, tag.AsJSDocAugmentsTag().ClassName)
case ast.KindJSDocSeeTag:
writeOptionalEntityName(&b, tag.AsJSDocSeeTag().NameExpression)
case ast.KindJSDocTemplateTag:
for i, tp := range tag.TypeParameters() {
if i != 0 {
b.WriteString(",")
}
writeOptionalEntityName(&b, tp.Name())
}
comments := tag.Comments()
if len(comments) != 0 {
if commentHasPrefix(comments, "```") {
b.WriteString("\n")
} else {
b.WriteString(" ")
if !commentHasPrefix(comments, "-") {
b.WriteString("— ")
}
}
comments := tag.Comments()
if len(comments) != 0 {
if commentHasPrefix(comments, "```") {
b.WriteString("\n")
} else {
b.WriteString(" ")
if !commentHasPrefix(comments, "-") {
b.WriteString("— ")
}
l.writeComments(&b, c, comments, isMarkdown)
}
l.writeComments(&b, c, comments, isMarkdown)
}
}
}
}
}
return quickInfo, b.String()
return b.String()
}

func formatQuickInfo(quickInfo string) string {
Expand Down
35 changes: 28 additions & 7 deletions internal/ls/signaturehelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (l *LanguageService) ProvideSignatureHelp(
position lsproto.Position,
context *lsproto.SignatureHelpContext,
clientOptions *lsproto.SignatureHelpClientCapabilities,
docFormat lsproto.MarkupKind,
) (lsproto.SignatureHelpResponse, error) {
program, sourceFile := l.getProgramAndFile(documentURI)
items := l.GetSignatureHelpItems(
Expand All @@ -52,7 +53,8 @@ func (l *LanguageService) ProvideSignatureHelp(
program,
sourceFile,
context,
clientOptions)
clientOptions,
docFormat)
return lsproto.SignatureHelpOrNull{SignatureHelp: items}, nil
}

Expand All @@ -63,6 +65,7 @@ func (l *LanguageService) GetSignatureHelpItems(
sourceFile *ast.SourceFile,
context *lsproto.SignatureHelpContext,
clientOptions *lsproto.SignatureHelpClientCapabilities,
docFormat lsproto.MarkupKind,
) *lsproto.SignatureHelp {
typeChecker, done := program.GetTypeCheckerForFile(ctx, sourceFile)
defer done()
Expand Down Expand Up @@ -140,7 +143,7 @@ func (l *LanguageService) GetSignatureHelpItems(

// return typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
if candidateInfo.candidateInfo != nil {
return createSignatureHelpItems(candidateInfo.candidateInfo.candidates, candidateInfo.candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker, onlyUseSyntacticOwners, clientOptions)
return l.createSignatureHelpItems(candidateInfo.candidateInfo.candidates, candidateInfo.candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker, onlyUseSyntacticOwners, clientOptions, docFormat)
}
return createTypeHelpItems(candidateInfo.typeInfo, argumentInfo, sourceFile, clientOptions, typeChecker)
}
Expand Down Expand Up @@ -202,7 +205,7 @@ func getTypeHelpItem(symbol *ast.Symbol, typeParameter []*checker.Type, enclosin
}
}

func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature *checker.Signature, argumentInfo *argumentListInfo, sourceFile *ast.SourceFile, c *checker.Checker, useFullPrefix bool, clientOptions *lsproto.SignatureHelpClientCapabilities) *lsproto.SignatureHelp {
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 {
enclosingDeclaration := getEnclosingDeclarationFromInvocation(argumentInfo.invocation)
if enclosingDeclaration == nil {
return nil
Expand All @@ -223,7 +226,7 @@ func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature
}
items := make([][]signatureInformation, len(candidates))
for i, candidateSignature := range candidates {
items[i] = getSignatureHelpItem(candidateSignature, argumentInfo.isTypeParameterList, callTargetDisplayParts.String(), enclosingDeclaration, sourceFile, c)
items[i] = l.getSignatureHelpItem(candidateSignature, argumentInfo.isTypeParameterList, callTargetDisplayParts.String(), enclosingDeclaration, sourceFile, c, docFormat)
}

selectedItemIndex := 0
Expand Down Expand Up @@ -262,9 +265,18 @@ func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature
for j, param := range item.Parameters {
parameters[j] = param.parameterInfo
}
var documentation *lsproto.StringOrMarkupContent
if item.Documentation != nil {
documentation = &lsproto.StringOrMarkupContent{
MarkupContent: &lsproto.MarkupContent{
Kind: docFormat,
Value: *item.Documentation,
},
}
}
signatureInformation[i] = &lsproto.SignatureInformation{
Label: item.Label,
Documentation: nil,
Documentation: documentation,
Parameters: &parameters,
}
}
Expand All @@ -291,7 +303,7 @@ func createSignatureHelpItems(candidates []*checker.Signature, resolvedSignature
return help
}

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

suffixDisplayParts := returnTypeToDisplayParts(candidate, c)

// Generate documentation from the signature's declaration
var documentation *string
if declaration := candidate.Declaration(); declaration != nil {
doc := l.getDocumentationFromDeclaration(c, declaration, docFormat)
if doc != "" {
documentation = &doc
}
}

result := make([]signatureInformation, len(infos))
for i, info := range infos {
var display strings.Builder
Expand All @@ -309,7 +330,7 @@ func getSignatureHelpItem(candidate *checker.Signature, isTypeParameterList bool
display.WriteString(suffixDisplayParts)
result[i] = signatureInformation{
Label: display.String(),
Documentation: nil,
Documentation: documentation,
Parameters: info.parameters,
IsVariadic: info.isVariadic,
}
Expand Down
16 changes: 16 additions & 0 deletions internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,7 @@ func (s *Server) handleSignatureHelp(ctx context.Context, languageService *ls.La
params.Position,
params.Context,
s.initializeParams.Capabilities.TextDocument.SignatureHelp,
getSignatureHelpDocumentationFormat(s.initializeParams),
)
}

Expand Down Expand Up @@ -1021,3 +1022,18 @@ func getHoverContentFormat(params *lsproto.InitializeParams) lsproto.MarkupKind
// Return the first (most preferred) format
return formats[0]
}

func getSignatureHelpDocumentationFormat(params *lsproto.InitializeParams) lsproto.MarkupKind {
if params == nil || params.Capabilities == nil || params.Capabilities.TextDocument == nil || params.Capabilities.TextDocument.SignatureHelp == nil ||
params.Capabilities.TextDocument.SignatureHelp.SignatureInformation == nil ||
params.Capabilities.TextDocument.SignatureHelp.SignatureInformation.DocumentationFormat == nil {
// Default to plaintext if no preference specified
return lsproto.MarkupKindPlainText
}
formats := *params.Capabilities.TextDocument.SignatureHelp.SignatureInformation.DocumentationFormat
if len(formats) == 0 {
return lsproto.MarkupKindPlainText
}
// Return the first (most preferred) format
return formats[0]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
// ^
// | ----------------------------------------------------------------------
// | foo(): module
// |
// |
// | *@returns* — :@nodefuel/web~Webserver~wsServer#hello} Websocket server object
// |
// | ----------------------------------------------------------------------
//
// /**
Expand Down Expand Up @@ -42,6 +46,10 @@
"signatures": [
{
"label": "foo(): module",
"documentation": {
"kind": "markdown",
"value": "\n\n*@returns* — :@nodefuel/web~Webserver~wsServer#hello} Websocket server object\n"
},
"parameters": []
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@
// ^
// | ----------------------------------------------------------------------
// | pathFilter(**basePath: String**, pattern: String, type: String, options: Object): any[]
// | Filters a path based on a regexp or glob pattern.
// |
// | *@param* `basePath` — The base path where the search will be performed.
// |
// |
// | *@param* `pattern` — A string defining a regexp of a glob pattern.
// |
// |
// | *@param* `type` — The search pattern type, can be a regexp or a glob.
// |
// |
// | *@param* `options` — A object containing options to the search.
// |
// |
// | *@return* — A list containing the filtered paths.
// |
// | ----------------------------------------------------------------------
[
{
Expand All @@ -31,6 +47,10 @@
"signatures": [
{
"label": "pathFilter(basePath: String, pattern: String, type: String, options: Object): any[]",
"documentation": {
"kind": "markdown",
"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"
},
"parameters": [
{
"label": "basePath: String"
Expand Down
Loading