Skip to content
Draft
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
39 changes: 39 additions & 0 deletions internal/fourslash/tests/quickInfoJsDocInheritDocTag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestQuickInfoJsDocInheritDocTag(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @noEmit: true
// @allowJs: true
// @Filename: quickInfoJsDocInheritDocTag.js
abstract class A {
/**
* A.f description
* @returns {string} A.f return value.
*/
public static f(props?: any): string {
throw new Error("Must be implemented by subclass");
}
}

class B extends A {
/**
* B.f description
* @inheritDoc
* @param {{ a: string; b: string; }} [props] description of props
*/
public static /**/f(props?: { a: string; b: string }): string {}
}
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyBaselineHover(t)
}
150 changes: 105 additions & 45 deletions internal/ls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,56 +75,100 @@ func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, de
return ""
}
isMarkdown := contentFormat == lsproto.MarkupKindMarkdown
var b strings.Builder
if jsdoc := getJSDocOrTag(c, 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.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())

jsdoc := getJSDocOrTag(c, declaration)
if jsdoc == nil {
docComments, docTags := l.getDocumentation(c, getInheritedJSDocOrTag(c, declaration), isMarkdown)
return docComments + docTags
}

if containsTypedefTag(jsdoc) {
return ""
}

var commentsBuilder strings.Builder
var tagsBuilder strings.Builder

if jsdoc.Kind == ast.KindJSDoc {
tags := jsdoc.AsJSDoc().Tags
if len(jsdoc.AsJSDoc().Comments()) == 0 || tags == nil || len(tags.Nodes) == 0 || core.Some(tags.Nodes, isInheritDocTag) {
docComments, docTags := l.getDocumentation(c, getInheritedJSDocOrTag(c, declaration), isMarkdown)
commentsBuilder.WriteString(docComments)
tagsBuilder.WriteString(docTags)
}
}

docComments, docTags := l.getDocumentation(c, jsdoc, isMarkdown)
if commentsBuilder.Len() > 0 && len(docComments) > 0 {
commentsBuilder.WriteString(" ")
}
commentsBuilder.WriteString(docComments)
tagsBuilder.WriteString(docTags)

commentsBuilder.WriteString(tagsBuilder.String())
return commentsBuilder.String()
}

func (l *LanguageService) getDocumentation(c *checker.Checker, jsdoc *ast.Node, isMarkdown bool) (comments, tags string) {
if jsdoc == nil {
return "", ""
}

var commnetsBuilder strings.Builder
var tagsBuilder strings.Builder

l.writeComments(&commnetsBuilder, 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
}
tagsBuilder.WriteString("\n\n")
if isMarkdown {
tagsBuilder.WriteString("*@")
tagsBuilder.WriteString(tag.TagName().Text())
tagsBuilder.WriteString("*")
} else {
tagsBuilder.WriteString("@")
tagsBuilder.WriteString(tag.TagName().Text())
}
switch tag.Kind {
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
writeOptionalEntityName(&tagsBuilder, tag.Name())
case ast.KindJSDocAugmentsTag:
writeOptionalEntityName(&tagsBuilder, tag.ClassName())
case ast.KindJSDocSeeTag:
writeOptionalEntityName(&tagsBuilder, tag.AsJSDocSeeTag().NameExpression)
case ast.KindJSDocTemplateTag:
for i, tp := range tag.TypeParameters() {
if i != 0 {
tagsBuilder.WriteString(",")
}
writeOptionalEntityName(&tagsBuilder, 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, "```") {
tagsBuilder.WriteString("\n")
} else {
tagsBuilder.WriteString(" ")
if !commentHasPrefix(comments, "-") {
tagsBuilder.WriteString("— ")
}
l.writeComments(&b, c, comments, isMarkdown)
}
l.writeComments(&tagsBuilder, c, comments, isMarkdown)
}
}
}
}
return b.String()
return commnetsBuilder.String(), tagsBuilder.String()
}

func isInheritDocTag(tag *ast.JSDocTag) bool {
return tag.TagName().Text() == "inheritDoc" || tag.TagName().Text() == "inheritdoc"
}

func formatQuickInfo(quickInfo string) string {
Expand Down Expand Up @@ -425,17 +469,33 @@ func getJSDocOrTag(c *checker.Checker, node *ast.Node) *ast.Node {
(ast.IsVariableDeclaration(node.Parent) || ast.IsPropertyDeclaration(node.Parent) || ast.IsPropertyAssignment(node.Parent)) && node.Parent.Initializer() == node:
return getJSDocOrTag(c, node.Parent)
}
return nil
}

func getInheritedJSDocOrTag(c *checker.Checker, node *ast.Node) *ast.Node {
if ast.IsGetAccessorDeclaration(node) || ast.IsSetAccessorDeclaration(node) {
return nil
}
if symbol := node.Symbol(); symbol != nil && node.Parent != nil && ast.IsClassOrInterfaceLike(node.Parent) {
isStatic := ast.HasStaticModifier(node)
for _, baseType := range c.GetBaseTypes(c.GetDeclaredTypeOfSymbol(node.Parent.Symbol())) {
t := baseType
if isStatic {
if isStatic && baseType.Symbol() != nil {
t = c.GetTypeOfSymbol(baseType.Symbol())
}
if prop := c.GetPropertyOfType(t, symbol.Name); prop != nil && prop.ValueDeclaration != nil {
if jsDoc := getJSDocOrTag(c, prop.ValueDeclaration); jsDoc != nil {
return jsDoc
jsdoc := getJSDocOrTag(c, prop.ValueDeclaration)
if jsdoc == nil {
return getInheritedJSDocOrTag(c, prop.ValueDeclaration)
}
tags := jsdoc.AsJSDoc().Tags
if tags == nil {
return jsdoc
}
if containsTypedefTag(jsdoc) {
return nil
}
return jsdoc
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
// | ```tsx
// | (method) SubClass.doSomethingUseful(mySpecificStuff?: { tiger: string; lion: string; }): string
// | ```
// | Useful description always applicable
// |
// | *@returns* — Useful description of return value always applicable.
// |
// |
// | *@inheritDoc*
Expand All @@ -65,6 +68,12 @@
// | ```tsx
// | (method) SubClass.func1(stuff1: any): void
// | ```
// | BaseClass.func1
// |
// | *@param* `stuff1` — BaseClass.func1.stuff1
// |
// |
// | *@returns* — BaseClass.func1.returns
// |
// |
// | *@inheritDoc*
Expand All @@ -88,7 +97,7 @@
// | ```tsx
// | (property) SubClass.someProperty: string
// | ```
// | text over tag
// | Applicable description always. text over tag
// |
// | *@inheritDoc* — text after tag
// |
Expand All @@ -108,7 +117,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) SubClass.doSomethingUseful(mySpecificStuff?: { tiger: string; lion: string; }): string\n```\n\n\n*@inheritDoc*\n\n*@param* `mySpecificStuff` — Description of my specific parameter.\n"
"value": "```tsx\n(method) SubClass.doSomethingUseful(mySpecificStuff?: { tiger: string; lion: string; }): string\n```\nUseful description always applicable\n\n*@returns* — Useful description of return value always applicable.\n\n\n*@inheritDoc*\n\n*@param* `mySpecificStuff` — Description of my specific parameter.\n"
},
"range": {
"start": {
Expand All @@ -135,7 +144,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) SubClass.func1(stuff1: any): void\n```\n\n\n*@inheritDoc*\n\n*@param* `stuff1` — SubClass.func1.stuff1\n\n\n*@returns* — SubClass.func1.returns\n"
"value": "```tsx\n(method) SubClass.func1(stuff1: any): void\n```\nBaseClass.func1\n\n*@param* `stuff1` — BaseClass.func1.stuff1\n\n\n*@returns* — BaseClass.func1.returns\n\n\n*@inheritDoc*\n\n*@param* `stuff1` — SubClass.func1.stuff1\n\n\n*@returns* — SubClass.func1.returns\n"
},
"range": {
"start": {
Expand All @@ -162,7 +171,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(property) SubClass.someProperty: string\n```\ntext over tag\n\n*@inheritDoc* — text after tag\n"
"value": "```tsx\n(property) SubClass.someProperty: string\n```\nApplicable description always. text over tag\n\n*@inheritDoc* — text after tag\n"
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// | ```tsx
// | (property) SubClass.prop: T
// | ```
// |
// | Base.prop
// |
// | *@inheritdoc* — SubClass.prop
// |
Expand All @@ -38,7 +38,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(property) SubClass.prop: T\n```\n\n\n*@inheritdoc* — SubClass.prop\n"
"value": "```tsx\n(property) SubClass.prop: T\n```\nBase.prop\n\n*@inheritdoc* — SubClass.prop\n"
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// | ```tsx
// | (property) SubClass.prop: string
// | ```
// |
// | Base.prop
// |
// | *@inheritdoc* — SubClass.prop
// |
Expand All @@ -39,7 +39,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(property) SubClass.prop: string\n```\n\n\n*@inheritdoc* — SubClass.prop\n"
"value": "```tsx\n(property) SubClass.prop: string\n```\nBase.prop\n\n*@inheritdoc* — SubClass.prop\n"
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// === QuickInfo ===
=== /quickInfoJsDocInheritDocTag.js ===
// abstract class A {
// /**
// * A.f description
// * @returns {string} A.f return value.
// */
// public static f(props?: any): string {
// throw new Error("Must be implemented by subclass");
// }
// }
//
// class B extends A {
// /**
// * B.f description
// * @inheritDoc
// * @param {{ a: string; b: string; }} [props] description of props
// */
// public static f(props?: { a: string; b: string }): string {}
// ^
// | ----------------------------------------------------------------------
// | ```tsx
// | (method) B.f(props?: { a: string; b: string; }): string
// | ```
// | A.f description B.f description
// |
// | *@returns* — A.f return value.
// |
// |
// | *@inheritDoc*
// |
// | *@param* `props` — description of props
// |
// | ----------------------------------------------------------------------
// }
//
[
{
"marker": {
"Position": 352,
"LSPosition": {
"line": 16,
"character": 16
},
"Name": "",
"Data": {}
},
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) B.f(props?: { a: string; b: string; }): string\n```\nA.f description B.f description\n\n*@returns* — A.f return value.\n\n\n*@inheritDoc*\n\n*@param* `props` — description of props\n"
},
"range": {
"start": {
"line": 16,
"character": 16
},
"end": {
"line": 16,
"character": 17
}
}
}
}
]
Loading