Skip to content

Commit fe41b2d

Browse files
authored
Full JSDoc support in LSP (#1702)
1 parent 3685064 commit fe41b2d

32 files changed

+517
-151
lines changed

internal/ast/ast.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9657,6 +9657,10 @@ func IsJSDocParameterTag(node *Node) bool {
96579657
return node.Kind == KindJSDocParameterTag
96589658
}
96599659

9660+
func IsJSDocPropertyTag(node *Node) bool {
9661+
return node.Kind == KindJSDocPropertyTag
9662+
}
9663+
96609664
// JSDocReturnTag
96619665
type JSDocReturnTag struct {
96629666
JSDocTagBase
@@ -10133,6 +10137,8 @@ func (node *JSDocCallbackTag) Clone(f NodeFactoryCoercible) *Node {
1013310137
return cloneNode(f.AsNodeFactory().NewJSDocCallbackTag(node.TagName, node.TypeExpression, node.FullName, node.Comment), node.AsNode(), f.AsNodeFactory().hooks)
1013410138
}
1013510139

10140+
func (node *JSDocCallbackTag) Name() *DeclarationName { return node.FullName }
10141+
1013610142
func IsJSDocCallbackTag(node *Node) bool {
1013710143
return node.Kind == KindJSDocCallbackTag
1013810144
}
@@ -10282,6 +10288,10 @@ func (node *JSDocSignature) Clone(f NodeFactoryCoercible) *Node {
1028210288
return cloneNode(f.AsNodeFactory().NewJSDocSignature(node.TypeParameters, node.Parameters, node.Type), node.AsNode(), f.AsNodeFactory().hooks)
1028310289
}
1028410290

10291+
func IsJSDocSignature(node *Node) bool {
10292+
return node.Kind == KindJSDocSignature
10293+
}
10294+
1028510295
// JSDocNameReference
1028610296
type JSDocNameReference struct {
1028710297
TypeNodeBase
@@ -10315,6 +10325,10 @@ func (node *JSDocNameReference) Clone(f NodeFactoryCoercible) *Node {
1031510325

1031610326
func (node *JSDocNameReference) Name() *EntityName { return node.name }
1031710327

10328+
func IsJSDocNameReference(node *Node) bool {
10329+
return node.Kind == KindJSDocNameReference
10330+
}
10331+
1031810332
// PatternAmbientModule
1031910333

1032010334
type PatternAmbientModule struct {

internal/ast/utilities.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,10 @@ func IsClassLike(node *Node) bool {
552552
return node.Kind == KindClassDeclaration || node.Kind == KindClassExpression
553553
}
554554

555+
func IsClassOrInterfaceLike(node *Node) bool {
556+
return node.Kind == KindClassDeclaration || node.Kind == KindClassExpression || node.Kind == KindInterfaceDeclaration
557+
}
558+
555559
func IsClassElement(node *Node) bool {
556560
switch node.Kind {
557561
case KindConstructor,
@@ -1899,13 +1903,11 @@ func IsExpressionNode(node *Node) bool {
18991903
for node.Parent.Kind == KindQualifiedName {
19001904
node = node.Parent
19011905
}
1902-
return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || isJSXTagName(node)
1903-
case KindJSDocMemberName:
1904-
return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || isJSXTagName(node)
1906+
return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || isJSXTagName(node)
19051907
case KindPrivateIdentifier:
19061908
return IsBinaryExpression(node.Parent) && node.Parent.AsBinaryExpression().Left == node && node.Parent.AsBinaryExpression().OperatorToken.Kind == KindInKeyword
19071909
case KindIdentifier:
1908-
if IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || isJSXTagName(node) {
1910+
if IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || isJSXTagName(node) {
19091911
return true
19101912
}
19111913
fallthrough
@@ -2045,7 +2047,9 @@ func isPartOfTypeNodeInParent(node *Node) bool {
20452047

20462048
func isPartOfTypeExpressionWithTypeArguments(node *Node) bool {
20472049
parent := node.Parent
2048-
return IsHeritageClause(parent) && (!IsClassLike(parent.Parent) || parent.AsHeritageClause().Token == KindImplementsKeyword)
2050+
return IsHeritageClause(parent) && (!IsClassLike(parent.Parent) || parent.AsHeritageClause().Token == KindImplementsKeyword) ||
2051+
IsJSDocImplementsTag(parent) ||
2052+
IsJSDocAugmentsTag(parent)
20492053
}
20502054

20512055
func IsJSDocLinkLike(node *Node) bool {
@@ -2620,12 +2624,12 @@ func IsParseTreeNode(node *Node) bool {
26202624
return node.Flags&NodeFlagsSynthesized == 0
26212625
}
26222626

2623-
// Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files
2624-
func GetNodeAtPosition(file *SourceFile, position int, isJavaScriptFile bool) *Node {
2627+
// Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only if requested
2628+
func GetNodeAtPosition(file *SourceFile, position int, includeJSDoc bool) *Node {
26252629
current := file.AsNode()
26262630
for {
26272631
var child *Node
2628-
if isJavaScriptFile {
2632+
if includeJSDoc {
26292633
for _, jsdoc := range current.JSDoc(file) {
26302634
if nodeContainsPosition(jsdoc, position) {
26312635
child = jsdoc
@@ -3867,3 +3871,9 @@ func GetRestIndicatorOfBindingOrAssignmentElement(bindingElement *Node) *Node {
38673871
}
38683872
return nil
38693873
}
3874+
3875+
func IsJSDocNameReferenceContext(node *Node) bool {
3876+
return node.Flags&NodeFlagsJSDoc != 0 && FindAncestor(node, func(node *Node) bool {
3877+
return IsJSDocNameReference(node) || IsJSDocLinkLike(node)
3878+
}) != nil
3879+
}

internal/astnav/tokens.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,27 @@ import (
99
)
1010

1111
func GetTouchingPropertyName(sourceFile *ast.SourceFile, position int) *ast.Node {
12-
return getTokenAtPosition(sourceFile, position, false /*allowPositionInLeadingTrivia*/, func(node *ast.Node) bool {
12+
return getReparsedNodeForNode(getTokenAtPosition(sourceFile, position, false /*allowPositionInLeadingTrivia*/, func(node *ast.Node) bool {
1313
return ast.IsPropertyNameLiteral(node) || ast.IsKeywordKind(node.Kind) || ast.IsPrivateIdentifier(node)
14-
})
14+
}))
15+
}
16+
17+
// If the given node is a declaration name node in a JSDoc comment that is subject to reparsing, return the declaration name node
18+
// for the corresponding reparsed construct. Otherwise, just return the node.
19+
func getReparsedNodeForNode(node *ast.Node) *ast.Node {
20+
if node.Flags&ast.NodeFlagsJSDoc != 0 && (ast.IsIdentifier(node) || ast.IsPrivateIdentifier(node)) {
21+
parent := node.Parent
22+
if (ast.IsJSDocTypedefTag(parent) || ast.IsJSDocCallbackTag(parent) || ast.IsJSDocPropertyTag(parent) || ast.IsJSDocParameterTag(parent) || ast.IsImportClause(parent) || ast.IsImportSpecifier(parent)) && parent.Name() == node {
23+
// Reparsing preserves the location of the name. Thus, a search at the position of the name with JSDoc excluded
24+
// finds the containing reparsed declaration node.
25+
if reparsed := ast.GetNodeAtPosition(ast.GetSourceFileOfNode(node), node.Pos(), false); reparsed != nil {
26+
if name := reparsed.Name(); name != nil && name.Pos() == node.Pos() {
27+
return name
28+
}
29+
}
30+
}
31+
}
32+
return node
1533
}
1634

1735
func GetTouchingToken(sourceFile *ast.SourceFile, position int) *ast.Node {

internal/checker/checker.go

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,10 +2368,11 @@ func (c *Checker) checkJSDocComment(node *ast.Node, location *ast.Node) {
23682368
func (c *Checker) resolveJSDocMemberName(name *ast.Node, location *ast.Node) *ast.Symbol {
23692369
if name != nil && ast.IsEntityName(name) {
23702370
meaning := ast.SymbolFlagsType | ast.SymbolFlagsNamespace | ast.SymbolFlagsValue
2371-
symbol := c.resolveEntityName(name, meaning, true /*ignoreErrors*/, true /*dontResolveAlias*/, location)
2372-
if symbol == nil && ast.IsQualifiedName(name) {
2373-
symbol := c.resolveJSDocMemberName(name.AsQualifiedName().Left, location)
2374-
if symbol != nil {
2371+
if symbol := c.resolveEntityName(name, meaning, true /*ignoreErrors*/, true /*dontResolveAlias*/, location); symbol != nil {
2372+
return symbol
2373+
}
2374+
if ast.IsQualifiedName(name) {
2375+
if symbol := c.resolveJSDocMemberName(name.AsQualifiedName().Left, location); symbol != nil {
23752376
var t *Type
23762377
if symbol.Flags&ast.SymbolFlagsValue != 0 {
23772378
proto := c.getPropertyOfType(c.getTypeOfSymbol(symbol), "prototype")
@@ -30203,19 +30204,15 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy
3020330204
return c.getSymbolOfDeclaration(grandParent)
3020430205
}
3020530206

30206-
if node.Kind == ast.KindIdentifier {
30207+
if ast.IsIdentifier(node) {
3020730208
if isInRightSideOfImportOrExportAssignment(node) {
3020830209
return c.getSymbolOfNameOrPropertyAccessExpression(node)
30209-
} else if parent.Kind == ast.KindBindingElement &&
30210-
grandParent.Kind == ast.KindObjectBindingPattern &&
30211-
node == parent.AsBindingElement().PropertyName {
30210+
} else if ast.IsBindingElement(parent) && ast.IsObjectBindingPattern(grandParent) && node == parent.PropertyName() {
3021230211
typeOfPattern := c.getTypeOfNode(grandParent)
30213-
propertyDeclaration := c.getPropertyOfType(typeOfPattern, node.Text())
30214-
30215-
if propertyDeclaration != nil {
30212+
if propertyDeclaration := c.getPropertyOfType(typeOfPattern, node.Text()); propertyDeclaration != nil {
3021630213
return propertyDeclaration
3021730214
}
30218-
} else if ast.IsMetaProperty(parent) && parent.AsMetaProperty().Name() == node {
30215+
} else if ast.IsMetaProperty(parent) && parent.Name() == node {
3021930216
metaProp := parent.AsMetaProperty()
3022030217
if metaProp.KeywordToken == ast.KindNewKeyword && node.Text() == "target" {
3022130218
// `target` in `new.target`
@@ -30230,6 +30227,14 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy
3023030227
}
3023130228
// no other meta properties are valid syntax, thus no others should have symbols
3023230229
return nil
30230+
} else if ast.IsJSDocParameterTag(parent) && parent.Name() == node {
30231+
if fn := ast.GetNodeAtPosition(ast.GetSourceFileOfNode(node), node.Pos(), false); fn != nil && ast.IsFunctionLike(fn) {
30232+
for _, param := range fn.Parameters() {
30233+
if param.Name().Text() == node.Text() {
30234+
return c.getSymbolOfNode(param)
30235+
}
30236+
}
30237+
}
3023330238
}
3023430239
}
3023530240

@@ -30418,28 +30423,32 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast
3041830423
// Missing entity name.
3041930424
return nil
3042030425
}
30421-
30422-
if name.Kind == ast.KindIdentifier {
30426+
isJSDoc := ast.IsJSDocNameReferenceContext(name)
30427+
if ast.IsIdentifier(name) {
3042330428
if ast.IsJsxTagName(name) && isJsxIntrinsicTagName(name) {
3042430429
symbol := c.getIntrinsicTagSymbol(name.Parent)
3042530430
return core.IfElse(symbol == c.unknownSymbol, nil, symbol)
3042630431
}
30427-
result := c.resolveEntityName(
30428-
name,
30429-
ast.SymbolFlagsValue, /*meaning*/
30430-
true, /*ignoreErrors*/
30431-
true, /*dontResolveAlias*/
30432-
nil /*location*/)
30432+
meaning := core.IfElse(isJSDoc, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, ast.SymbolFlagsValue)
30433+
result := c.resolveEntityName(name, meaning, true /*ignoreErrors*/, true /*dontResolveAlias*/, nil /*location*/)
30434+
if result == nil && isJSDoc {
30435+
if container := ast.FindAncestor(name, ast.IsClassOrInterfaceLike); container != nil {
30436+
symbol := c.getSymbolOfDeclaration(container)
30437+
// Handle unqualified references to class static members and class or interface instance members
30438+
if result = c.getMergedSymbol(c.getSymbol(c.getExportsOfSymbol(symbol), name.Text(), meaning)); result == nil {
30439+
result = c.getPropertyOfType(c.getDeclaredTypeOfSymbol(symbol), name.Text())
30440+
}
30441+
}
30442+
}
3043330443
return result
3043430444
} else if ast.IsPrivateIdentifier(name) {
3043530445
return c.getSymbolForPrivateIdentifierExpression(name)
30436-
} else if name.Kind == ast.KindPropertyAccessExpression || name.Kind == ast.KindQualifiedName {
30446+
} else if ast.IsPropertyAccessExpression(name) || ast.IsQualifiedName(name) {
3043730447
links := c.symbolNodeLinks.Get(name)
3043830448
if links.resolvedSymbol != nil {
3043930449
return links.resolvedSymbol
3044030450
}
30441-
30442-
if name.Kind == ast.KindPropertyAccessExpression {
30451+
if ast.IsPropertyAccessExpression(name) {
3044330452
c.checkPropertyAccessExpression(name, CheckModeNormal, false /*writeOnly*/)
3044430453
if links.resolvedSymbol == nil {
3044530454
links.resolvedSymbol = c.getApplicableIndexSymbol(
@@ -30450,7 +30459,9 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast
3045030459
} else {
3045130460
c.checkQualifiedName(name, CheckModeNormal)
3045230461
}
30453-
30462+
if links.resolvedSymbol == nil && isJSDoc && ast.IsQualifiedName(name) {
30463+
return c.resolveJSDocMemberName(name, nil)
30464+
}
3045430465
return links.resolvedSymbol
3045530466
}
3045630467
} else if ast.IsEntityName(name) && isTypeReferenceIdentifier(name) {

internal/ls/symbols.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ func (l *LanguageService) ProvideDocumentSymbols(ctx context.Context, documentUR
2626
func (l *LanguageService) getDocumentSymbolsForChildren(ctx context.Context, node *ast.Node) []*lsproto.DocumentSymbol {
2727
var symbols []*lsproto.DocumentSymbol
2828
addSymbolForNode := func(node *ast.Node, children []*lsproto.DocumentSymbol) {
29-
symbol := l.newDocumentSymbol(node, children)
30-
if symbol != nil {
31-
symbols = append(symbols, symbol)
29+
if node.Flags&ast.NodeFlagsReparsed == 0 {
30+
symbol := l.newDocumentSymbol(node, children)
31+
if symbol != nil {
32+
symbols = append(symbols, symbol)
33+
}
3234
}
3335
}
3436
var visit func(*ast.Node) bool

internal/ls/utilities.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,14 +1259,14 @@ func getAdjustedLocationForExportDeclaration(node *ast.ExportDeclaration, forRen
12591259

12601260
func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
12611261
// todo: check if this function needs to be changed for jsdoc updates
1262-
12631262
node = getAdjustedLocation(node, false /*forRename*/, nil)
12641263
parent := node.Parent
1265-
if node.Kind == ast.KindSourceFile {
1264+
switch {
1265+
case ast.IsSourceFile(node):
12661266
return ast.SemanticMeaningValue
1267-
} else if ast.NodeKindIs(node, ast.KindExportAssignment, ast.KindExportSpecifier, ast.KindExternalModuleReference, ast.KindImportSpecifier, ast.KindImportClause) || parent.Kind == ast.KindImportEqualsDeclaration && node == parent.Name() {
1267+
case ast.NodeKindIs(node, ast.KindExportAssignment, ast.KindExportSpecifier, ast.KindExternalModuleReference, ast.KindImportSpecifier, ast.KindImportClause) || parent.Kind == ast.KindImportEqualsDeclaration && node == parent.Name():
12681268
return ast.SemanticMeaningAll
1269-
} else if isInRightSideOfInternalImportEqualsDeclaration(node) {
1269+
case isInRightSideOfInternalImportEqualsDeclaration(node):
12701270
// import a = |b|; // Namespace
12711271
// import a = |b.c|; // Value, type, namespace
12721272
// import a = |b.c|.d; // Namespace
@@ -1278,22 +1278,20 @@ func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
12781278
return ast.SemanticMeaningNamespace
12791279
}
12801280
return ast.SemanticMeaningAll
1281-
} else if ast.IsDeclarationName(node) {
1281+
case ast.IsDeclarationName(node):
12821282
return getMeaningFromDeclaration(parent)
1283-
} else if ast.IsEntityName(node) && ast.FindAncestor(node, func(*ast.Node) bool {
1284-
return node.Kind == ast.KindJSDocNameReference || ast.IsJSDocLinkLike(node) || node.Kind == ast.KindJSDocMemberName
1285-
}) != nil {
1283+
case ast.IsEntityName(node) && ast.IsJSDocNameReferenceContext(node):
12861284
return ast.SemanticMeaningAll
1287-
} else if isTypeReference(node) {
1285+
case isTypeReference(node):
12881286
return ast.SemanticMeaningType
1289-
} else if isNamespaceReference(node) {
1287+
case isNamespaceReference(node):
12901288
return ast.SemanticMeaningNamespace
1291-
} else if parent.Kind == ast.KindTypeParameter {
1289+
case ast.IsTypeParameterDeclaration(parent):
12921290
return ast.SemanticMeaningType
1293-
} else if parent.Kind == ast.KindLiteralType {
1291+
case ast.IsLiteralTypeNode(parent):
12941292
// This might be T["name"], which is actually referencing a property and not a type. So allow both meanings.
12951293
return ast.SemanticMeaningType | ast.SemanticMeaningValue
1296-
} else {
1294+
default:
12971295
return ast.SemanticMeaningValue
12981296
}
12991297
}

internal/parser/jsdoc.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ func (p *Parser) parseTypedefTag(start int, tagName *ast.IdentifierNode, indent
918918
if childTypeTag != nil && childTypeTag.TypeExpression != nil && !isObjectOrObjectArrayTypeReference(childTypeTag.TypeExpression.Type()) {
919919
typeExpression = childTypeTag.TypeExpression
920920
} else {
921-
typeExpression = p.finishNode(jsdocTypeLiteral, start)
921+
typeExpression = p.finishNode(jsdocTypeLiteral, jsdocPropertyTags[0].Pos())
922922
}
923923
}
924924
}
@@ -989,7 +989,7 @@ func (p *Parser) parseCallbackTag(start int, tagName *ast.IdentifierNode, indent
989989
fullName := p.parseJSDocIdentifierName(nil)
990990
p.skipWhitespace()
991991
comment := p.parseTagComments(indent, nil)
992-
typeExpression := p.parseJSDocSignature(start, indent)
992+
typeExpression := p.parseJSDocSignature(p.nodePos(), indent)
993993
if comment == nil {
994994
comment = p.parseTrailingTagComments(start, p.nodePos(), indent, indentText)
995995
}

internal/parser/reparser.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD
191191
if jsSignature.Type() != nil && jsSignature.Type().AsJSDocReturnTag().TypeExpression != nil {
192192
signature.FunctionLikeData().Type = p.factory.DeepCloneReparse(jsSignature.Type().AsJSDocReturnTag().TypeExpression.Type())
193193
}
194-
loc := tag
194+
loc := jsSignature
195195
if tag.Kind == ast.KindJSDocOverloadTag {
196196
loc = tag.AsJSDocOverloadTag().TagName
197197
}
@@ -213,7 +213,7 @@ func (p *Parser) reparseJSDocTypeLiteral(t *ast.TypeNode) *ast.Node {
213213
if name.Kind == ast.KindQualifiedName {
214214
name = name.AsQualifiedName().Right
215215
}
216-
property := p.factory.NewPropertySignatureDeclaration(nil, name, p.makeQuestionIfOptional(jsprop), nil, nil)
216+
property := p.factory.NewPropertySignatureDeclaration(nil, p.factory.DeepCloneReparse(name), p.makeQuestionIfOptional(jsprop), nil, nil)
217217
if jsprop.TypeExpression != nil {
218218
property.AsPropertySignatureDeclaration().Type = p.reparseJSDocTypeLiteral(jsprop.TypeExpression.Type())
219219
}

testdata/baselines/reference/fourslash/findAllRef/FindAllReferencesJsOverloadedFunctionParameter.baseline.jsonc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
// === findAllReferences ===
22
// === /foo.js ===
33

4-
// --- (line: 9) skipped ---
5-
// * @param {unknown} x
4+
// /**
5+
// * @overload
6+
// * @param {number} [|x|]
7+
// * @returns {number}
8+
// *
9+
// * @overload
10+
// * @param {string} [|x|]
11+
// * @returns {string}
12+
// *
13+
// * @param {unknown} [|x|]
614
// * @returns {unknown}
715
// */
816
// function foo([|x|]/*FIND ALL REFS*/) {

0 commit comments

Comments
 (0)