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
4 changes: 4 additions & 0 deletions internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func (c *Checker) GetPropertyOfType(t *Type, name string) *ast.Symbol {
return c.getPropertyOfType(t, name)
}

func (c *Checker) GetBaseTypes(t *Type) []*Type {
return c.getBaseTypes(t)
}

func (c *Checker) TypeHasCallOrConstructSignatures(t *Type) bool {
return c.typeHasCallOrConstructSignatures(t)
}
Expand Down
61 changes: 61 additions & 0 deletions internal/fourslash/tests/hoverInheritedJSDoc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package fourslash_test

import (
"testing"

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

func TestHoverInheritedJSDocInterface(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `
interface foo {
/** base jsdoc */
bar(k: string): number;
/** other jsdoc */
other: 24;
}

interface bar extends foo {
bar(k: string | symbol): number | 99;
}

declare const f: foo;
declare const b: bar;

f./*1*/bar;
f./*2*/other;
b./*3*/bar;
b./*4*/other;
`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineHover(t)
}

func TestHoverInheritedJSDocClass(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `
declare class thing {
/** doc comment */
method(s: string): void;
}

declare class potato extends thing {
method(s: "1234"): void;
}

declare const t: thing;
declare const p: potato;

t./*1*/method;
p./*2*/method;
`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
// t.method should show "doc comment"
f.VerifyQuickInfoAt(t, "1", "(method) thing.method(s: string): void", "doc comment")
// p.method should inherit "doc comment" from thing
f.VerifyQuickInfoAt(t, "2", "(method) potato.method(s: \"1234\"): void", "doc comment")
}
62 changes: 59 additions & 3 deletions internal/ls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,23 @@ func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Check
if quickInfo == "" {
return "", ""
}
return quickInfo, l.getDocumentationFromDeclaration(c, declaration, contentFormat)
return quickInfo, l.getDocumentationFromDeclaration(c, symbol, declaration, contentFormat)
}

func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, declaration *ast.Node, contentFormat lsproto.MarkupKind) string {
func (l *LanguageService) getDocumentationFromDeclaration(c *checker.Checker, symbol *ast.Symbol, declaration *ast.Node, contentFormat lsproto.MarkupKind) string {
if declaration == nil {
return ""
}
isMarkdown := contentFormat == lsproto.MarkupKindMarkdown
var b strings.Builder
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
jsdoc := getJSDocOrTag(declaration)

// If there's no JSDoc on the current declaration, try to find it in base types
if jsdoc == nil && symbol != nil && symbol.Parent != nil {
jsdoc = l.getJSDocFromBaseTypes(c, symbol)
}

if jsdoc != nil && !containsTypedefTag(jsdoc) {
l.writeComments(&b, c, jsdoc.Comments(), isMarkdown)
if jsdoc.Kind == ast.KindJSDoc {
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
Expand Down Expand Up @@ -561,3 +568,52 @@ func writeEntityNameParts(b *strings.Builder, node *ast.Node) {
writeEntityNameParts(b, node.Name())
}
}

// getJSDocFromBaseTypes searches for JSDoc comments in base types when the current symbol doesn't have one
func (l *LanguageService) getJSDocFromBaseTypes(c *checker.Checker, symbol *ast.Symbol) *ast.Node {
Copy link
Member

Choose a reason for hiding this comment

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

@copilot This function does not exist in the old codebase. What does this code correspond to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This corresponds to the findBaseOfDeclaration function in src/services/services.ts (lines ~3565-3575 in the TypeScript codebase).

The TypeScript implementation:

function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined {
    const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent;
    if (!classOrInterfaceDeclaration) return;

    const isStaticMember = hasStaticModifier(declaration);
    return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => {
        const baseType = checker.getTypeAtLocation(superTypeNode);
        const type = isStaticMember && baseType.symbol ? checker.getTypeOfSymbol(baseType.symbol) : baseType;
        const symbol = checker.getPropertyOfType(type, declaration.symbol.name);
        return symbol ? cb(symbol) : undefined;
    });
}

Our Go implementation achieves the same goal: traverse base types to find JSDoc from parent declarations when the current declaration lacks documentation.

if symbol == nil || symbol.Parent == nil {
return nil
}

// Get the parent type (the class or interface containing this symbol)
parentType := c.GetDeclaredTypeOfSymbol(symbol.Parent)
if parentType == nil {
return nil
}

// Only class and interface types have base types
if symbol.Parent.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) == 0 {
return nil
}

// Get base types (parent classes or extended interfaces)
baseTypes := c.GetBaseTypes(parentType)
if len(baseTypes) == 0 {
return nil
}

// Search each base type for a property/method with the same name
symbolName := symbol.Name
for _, baseType := range baseTypes {
// Get the property from the base type
baseProp := c.GetPropertyOfType(baseType, symbolName)
if baseProp == nil {
continue
}

// Check if the base property has a declaration with JSDoc
if baseProp.ValueDeclaration != nil {
if jsdoc := getJSDocOrTag(baseProp.ValueDeclaration); jsdoc != nil {
return jsdoc
}
}

// If not found in this base, recursively check its bases
if jsdoc := l.getJSDocFromBaseTypes(c, baseProp); jsdoc != nil {
return jsdoc
}
}

return nil
}

2 changes: 1 addition & 1 deletion internal/ls/signaturehelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ func (l *LanguageService) getSignatureHelpItem(candidate *checker.Signature, isT
// Generate documentation from the signature's declaration
var documentation *string
if declaration := candidate.Declaration(); declaration != nil {
doc := l.getDocumentationFromDeclaration(c, declaration, docFormat)
doc := l.getDocumentationFromDeclaration(c, nil, declaration, docFormat)
if doc != "" {
documentation = &doc
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// === QuickInfo ===
=== /hoverInheritedJSDocInterface.ts ===
// interface foo {
// /** base jsdoc */
// bar(k: string): number;
// /** other jsdoc */
// other: 24;
// }
//
// interface bar extends foo {
// bar(k: string | symbol): number | 99;
// }
//
// declare const f: foo;
// declare const b: bar;
//
// f.bar;
// ^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | (method) foo.bar(k: string): number
// | ```
// | base jsdoc
// | ----------------------------------------------------------------------
// f.other;
// ^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | (property) foo.other: 24
// | ```
// | other jsdoc
// | ----------------------------------------------------------------------
// b.bar;
// ^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | (method) bar.bar(k: string | symbol): number
// | ```
// | base jsdoc
// | ----------------------------------------------------------------------
// b.other;
// ^^^^^
// | ----------------------------------------------------------------------
// | ```tsx
// | (property) foo.other: 24
// | ```
// | other jsdoc
// | ----------------------------------------------------------------------
//
[
{
"marker": {
"Position": 217,
"LSPosition": {
"line": 14,
"character": 2
},
"Name": "1",
"Data": {}
},
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) foo.bar(k: string): number\n```\nbase jsdoc"
},
"range": {
"start": {
"line": 14,
"character": 2
},
"end": {
"line": 14,
"character": 5
}
}
}
},
{
"marker": {
"Position": 224,
"LSPosition": {
"line": 15,
"character": 2
},
"Name": "2",
"Data": {}
},
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(property) foo.other: 24\n```\nother jsdoc"
},
"range": {
"start": {
"line": 15,
"character": 2
},
"end": {
"line": 15,
"character": 7
}
}
}
},
{
"marker": {
"Position": 233,
"LSPosition": {
"line": 16,
"character": 2
},
"Name": "3",
"Data": {}
},
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) bar.bar(k: string | symbol): number\n```\nbase jsdoc"
},
"range": {
"start": {
"line": 16,
"character": 2
},
"end": {
"line": 16,
"character": 5
}
}
}
},
{
"marker": {
"Position": 240,
"LSPosition": {
"line": 17,
"character": 2
},
"Name": "4",
"Data": {}
},
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(property) foo.other: 24\n```\nother jsdoc"
},
"range": {
"start": {
"line": 17,
"character": 2
},
"end": {
"line": 17,
"character": 7
}
}
}
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// | ```tsx
// | (method) B.method(): void
// | ```
// |
// | Method documentation.
// | ----------------------------------------------------------------------
[
{
Expand All @@ -34,7 +34,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) B.method(): void\n```\n"
"value": "```tsx\n(method) B.method(): void\n```\nMethod documentation."
},
"range": {
"start": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// | ```tsx
// | (method) B.method(): void
// | ```
// |
// | Method documentation.
// | ----------------------------------------------------------------------
[
{
Expand All @@ -34,7 +34,7 @@
"item": {
"contents": {
"kind": "markdown",
"value": "```tsx\n(method) B.method(): void\n```\n"
"value": "```tsx\n(method) B.method(): void\n```\nMethod documentation."
},
"range": {
"start": {
Expand Down
Loading
Loading