Skip to content

Commit fc34cd0

Browse files
gabrittosandersn
authored andcommitted
JSDoc completions (microsoft#1561)
Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 5f542b2 commit fc34cd0

33 files changed

+1050
-155
lines changed

internal/ast/ast.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,7 @@ func (n *Node) IsTypeOnly() bool {
897897
return false
898898
}
899899

900+
// If updating this function, also update `hasComment`.
900901
func (n *Node) CommentList() *NodeList {
901902
switch n.Kind {
902903
case KindJSDoc:
@@ -1033,6 +1034,8 @@ func (n *Node) ElementList() *NodeList {
10331034
return n.AsNamedImports().Elements
10341035
case KindNamedExports:
10351036
return n.AsNamedExports().Elements
1037+
case KindObjectBindingPattern, KindArrayBindingPattern:
1038+
return n.AsBindingPattern().Elements
10361039
}
10371040

10381041
panic("Unhandled case in Node.ElementList: " + n.Kind.String())
@@ -1060,6 +1063,32 @@ func (n *Node) QuestionDotToken() *Node {
10601063
panic("Unhandled case in Node.QuestionDotToken: " + n.Kind.String())
10611064
}
10621065

1066+
func (n *Node) TypeExpression() *Node {
1067+
switch n.Kind {
1068+
case KindJSDocPropertyTag, KindJSDocParameterTag:
1069+
return n.AsJSDocParameterOrPropertyTag().TypeExpression
1070+
case KindJSDocReturnTag:
1071+
return n.AsJSDocReturnTag().TypeExpression
1072+
case KindJSDocTypeTag:
1073+
return n.AsJSDocTypeTag().TypeExpression
1074+
case KindJSDocTypedefTag:
1075+
return n.AsJSDocTypedefTag().TypeExpression
1076+
case KindJSDocSatisfiesTag:
1077+
return n.AsJSDocSatisfiesTag().TypeExpression
1078+
}
1079+
panic("Unhandled case in Node.TypeExpression: " + n.Kind.String())
1080+
}
1081+
1082+
func (n *Node) ClassName() *Node {
1083+
switch n.Kind {
1084+
case KindJSDocAugmentsTag:
1085+
return n.AsJSDocAugmentsTag().ClassName
1086+
case KindJSDocImplementsTag:
1087+
return n.AsJSDocImplementsTag().ClassName
1088+
}
1089+
panic("Unhandled case in Node.ClassName: " + n.Kind.String())
1090+
}
1091+
10631092
// Determines if `n` contains `descendant` by walking up the `Parent` pointers from `descendant`. This method panics if
10641093
// `descendant` or one of its ancestors is not parented except when that node is a `SourceFile`.
10651094
func (n *Node) Contains(descendant *Node) bool {
@@ -2049,6 +2078,8 @@ type (
20492078
NamedExportsNode = Node
20502079
UnionType = Node
20512080
LiteralType = Node
2081+
JSDocNode = Node
2082+
BindingPatternNode = Node
20522083
)
20532084

20542085
type (
@@ -9535,6 +9566,10 @@ func (node *JSDocTemplateTag) Clone(f NodeFactoryCoercible) *Node {
95359566
return cloneNode(f.AsNodeFactory().NewJSDocTemplateTag(node.TagName, node.Constraint, node.TypeParameters, node.Comment), node.AsNode(), f.AsNodeFactory().hooks)
95369567
}
95379568

9569+
func IsJSDocTemplateTag(n *Node) bool {
9570+
return n.Kind == KindJSDocTemplateTag
9571+
}
9572+
95389573
// JSDocParameterOrPropertyTag
95399574
type JSDocParameterOrPropertyTag struct {
95409575
JSDocTagBase
@@ -9600,6 +9635,10 @@ func (node *JSDocParameterOrPropertyTag) Clone(f NodeFactoryCoercible) *Node {
96009635

96019636
func (node *JSDocParameterOrPropertyTag) Name() *EntityName { return node.name }
96029637

9638+
func IsJSDocParameterTag(node *Node) bool {
9639+
return node.Kind == KindJSDocParameterTag
9640+
}
9641+
96039642
// JSDocReturnTag
96049643
type JSDocReturnTag struct {
96059644
JSDocTagBase
@@ -9893,6 +9932,10 @@ func (node *JSDocImplementsTag) Clone(f NodeFactoryCoercible) *Node {
98939932
return cloneNode(f.AsNodeFactory().NewJSDocImplementsTag(node.TagName, node.ClassName, node.Comment), node.AsNode(), f.AsNodeFactory().hooks)
98949933
}
98959934

9935+
func IsJSDocImplementsTag(node *Node) bool {
9936+
return node.Kind == KindJSDocImplementsTag
9937+
}
9938+
98969939
// JSDocAugmentsTag
98979940
type JSDocAugmentsTag struct {
98989941
JSDocTagBase

internal/ast/utilities.go

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,16 +2103,6 @@ func TryGetTextOfPropertyName(name *Node) (string, bool) {
21032103
return "", false
21042104
}
21052105

2106-
// True if node is of a JSDoc kind that may contain comment text.
2107-
func IsJSDocCommentContainingNode(node *Node) bool {
2108-
return node.Kind == KindJSDoc ||
2109-
node.Kind == KindJSDocText ||
2110-
node.Kind == KindJSDocTypeLiteral ||
2111-
node.Kind == KindJSDocSignature ||
2112-
IsJSDocLinkLike(node) ||
2113-
IsJSDocTag(node)
2114-
}
2115-
21162106
func IsJSDocNode(node *Node) bool {
21172107
return node.Kind >= KindFirstJSDocNode && node.Kind <= KindLastJSDocNode
21182108
}
@@ -3017,13 +3007,27 @@ func IsTypeKeywordToken(node *Node) bool {
30173007
return node.Kind == KindTypeKeyword
30183008
}
30193009

3020-
// If node is a single comment JSDoc, we do not visit the comment node list.
3021-
func IsJSDocSingleCommentNodeList(parent *Node, nodeList *NodeList) bool {
3022-
return IsJSDocSingleCommentNode(parent) && nodeList == parent.AsJSDoc().Comment
3010+
// See `IsJSDocSingleCommentNode`.
3011+
func IsJSDocSingleCommentNodeList(nodeList *NodeList) bool {
3012+
if nodeList == nil || len(nodeList.Nodes) == 0 {
3013+
return false
3014+
}
3015+
parent := nodeList.Nodes[0].Parent
3016+
return IsJSDocSingleCommentNode(parent) && nodeList == parent.CommentList()
3017+
}
3018+
3019+
// See `IsJSDocSingleCommentNode`.
3020+
func IsJSDocSingleCommentNodeComment(node *Node) bool {
3021+
if node == nil || node.Parent == nil {
3022+
return false
3023+
}
3024+
return IsJSDocSingleCommentNode(node.Parent) && node == node.Parent.CommentList().Nodes[0]
30233025
}
30243026

3027+
// In Strada, if a JSDoc node has a single comment, that comment is represented as a string property
3028+
// as a simplification, and therefore that comment is not visited by `forEachChild`.
30253029
func IsJSDocSingleCommentNode(node *Node) bool {
3026-
return node.Kind == KindJSDoc && node.AsJSDoc().Comment != nil && len(node.AsJSDoc().Comment.Nodes) == 1
3030+
return hasComment(node.Kind) && node.CommentList() != nil && len(node.CommentList().Nodes) == 1
30273031
}
30283032

30293033
func IsValidTypeOnlyAliasUseSite(useSite *Node) bool {
@@ -3635,3 +3639,18 @@ func GetSemanticJsxChildren(children []*JsxChild) []*JsxChild {
36353639
}
36363640
})
36373641
}
3642+
3643+
// Returns true if the node kind has a comment property.
3644+
func hasComment(kind Kind) bool {
3645+
switch kind {
3646+
case KindJSDoc, KindJSDocTag, KindJSDocAugmentsTag, KindJSDocImplementsTag,
3647+
KindJSDocDeprecatedTag, KindJSDocPublicTag, KindJSDocPrivateTag, KindJSDocProtectedTag,
3648+
KindJSDocReadonlyTag, KindJSDocOverrideTag, KindJSDocCallbackTag, KindJSDocOverloadTag,
3649+
KindJSDocParameterTag, KindJSDocPropertyTag, KindJSDocReturnTag, KindJSDocThisTag,
3650+
KindJSDocTypeTag, KindJSDocTemplateTag, KindJSDocTypedefTag, KindJSDocSeeTag,
3651+
KindJSDocSatisfiesTag, KindJSDocImportTag:
3652+
return true
3653+
default:
3654+
return false
3655+
}
3656+
}

internal/astnav/tokens.go

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,8 @@ func getTokenAtPosition(
125125
return nodeList
126126
}
127127

128-
nodeVisitor := getNodeVisitor(visitNode, visitNodeList)
129-
130128
for {
131-
VisitEachChildAndJSDoc(current, sourceFile, nodeVisitor)
129+
VisitEachChildAndJSDoc(current, sourceFile, visitNode, visitNodeList)
132130
// If prevSubtree was set on the last iteration, it ends at the target position.
133131
// Check if the rightmost token of prevSubtree should be returned based on the
134132
// `includePrecedingTokenAtEndPosition` callback.
@@ -146,7 +144,7 @@ func getTokenAtPosition(
146144
// we can in the AST. We've either found a token, or we need to run the scanner
147145
// to construct one that isn't stored in the AST.
148146
if next == nil {
149-
if ast.IsTokenKind(current.Kind) || ast.IsJSDocCommentContainingNode(current) {
147+
if ast.IsTokenKind(current.Kind) || shouldSkipChild(current) {
150148
return current
151149
}
152150
scanner := scanner.GetScannerForSourceFile(sourceFile, left)
@@ -217,7 +215,13 @@ func findRightmostNode(node *ast.Node) *ast.Node {
217215
}
218216
}
219217

220-
func VisitEachChildAndJSDoc(node *ast.Node, sourceFile *ast.SourceFile, visitor *ast.NodeVisitor) {
218+
func VisitEachChildAndJSDoc(
219+
node *ast.Node,
220+
sourceFile *ast.SourceFile,
221+
visitNode func(*ast.Node, *ast.NodeVisitor) *ast.Node,
222+
visitNodes func(*ast.NodeList, *ast.NodeVisitor) *ast.NodeList,
223+
) {
224+
visitor := getNodeVisitor(visitNode, visitNodes)
221225
if node.Flags&ast.NodeFlagsHasJSDoc != 0 {
222226
for _, jsdoc := range node.JSDoc(sourceFile) {
223227
if visitor.Hooks.VisitNode != nil {
@@ -275,9 +279,6 @@ func FindPrecedingTokenEx(sourceFile *ast.SourceFile, position int, startNode *a
275279
}
276280
if nodeList != nil && len(nodeList.Nodes) > 0 {
277281
nodes := nodeList.Nodes
278-
if ast.IsJSDocSingleCommentNodeList(n, nodeList) {
279-
return nodeList
280-
}
281282
index, match := core.BinarySearchUniqueFunc(nodes, func(middle int, _ *ast.Node) int {
282283
// synthetic jsdoc nodes should have jsdocNode.End() <= n.Pos()
283284
if nodes[middle].Flags&ast.NodeFlagsReparsed != 0 {
@@ -308,8 +309,7 @@ func FindPrecedingTokenEx(sourceFile *ast.SourceFile, position int, startNode *a
308309
}
309310
return nodeList
310311
}
311-
nodeVisitor := getNodeVisitor(visitNode, visitNodes)
312-
VisitEachChildAndJSDoc(n, sourceFile, nodeVisitor)
312+
VisitEachChildAndJSDoc(n, sourceFile, visitNode, visitNodes)
313313

314314
if foundChild != nil {
315315
// Note that the span of a node's tokens is [getStartOfNode(node, ...), node.end).
@@ -420,9 +420,6 @@ func findRightmostValidToken(endPos int, sourceFile *ast.SourceFile, containingN
420420
}
421421
visitNodes := func(nodeList *ast.NodeList, _ *ast.NodeVisitor) *ast.NodeList {
422422
if nodeList != nil && len(nodeList.Nodes) > 0 {
423-
if ast.IsJSDocSingleCommentNodeList(n, nodeList) {
424-
return nodeList
425-
}
426423
hasChildren = true
427424
index, _ := core.BinarySearchUniqueFunc(nodeList.Nodes, func(middle int, node *ast.Node) int {
428425
if node.End() > endPos {
@@ -450,16 +447,15 @@ func findRightmostValidToken(endPos int, sourceFile *ast.SourceFile, containingN
450447
}
451448
return nodeList
452449
}
453-
nodeVisitor := getNodeVisitor(visitNode, visitNodes)
454-
VisitEachChildAndJSDoc(n, sourceFile, nodeVisitor)
450+
VisitEachChildAndJSDoc(n, sourceFile, visitNode, visitNodes)
455451

456452
// Three cases:
457453
// 1. The answer is a token of `rightmostValidNode`.
458454
// 2. The answer is one of the unvisited tokens that occur after the rightmost valid node.
459455
// 3. The current node is a childless, token-less node. The answer is the current node.
460456

461457
// Case 2: Look at unvisited trailing tokens that occur in between the rightmost visited nodes.
462-
if !ast.IsJSDocCommentContainingNode(n) { // JSDoc nodes don't include trivia tokens as children.
458+
if !shouldSkipChild(n) { // JSDoc nodes don't include trivia tokens as children.
463459
var startPos int
464460
if rightmostValidNode != nil {
465461
startPos = rightmostValidNode.End()
@@ -563,8 +559,7 @@ func FindNextToken(previousToken *ast.Node, parent *ast.Node, file *ast.SourceFi
563559
}
564560
return nodeList
565561
}
566-
nodeVisitor := getNodeVisitor(visitNode, visitNodes)
567-
VisitEachChildAndJSDoc(n, file, nodeVisitor)
562+
VisitEachChildAndJSDoc(n, file, visitNode, visitNodes)
568563
// Cases:
569564
// 1. no answer exists
570565
// 2. answer is an unvisited token
@@ -597,15 +592,44 @@ func getNodeVisitor(
597592
visitNode func(*ast.Node, *ast.NodeVisitor) *ast.Node,
598593
visitNodes func(*ast.NodeList, *ast.NodeVisitor) *ast.NodeList,
599594
) *ast.NodeVisitor {
595+
var wrappedVisitNode func(*ast.Node, *ast.NodeVisitor) *ast.Node
596+
var wrappedVisitNodes func(*ast.NodeList, *ast.NodeVisitor) *ast.NodeList
597+
if visitNode != nil {
598+
wrappedVisitNode = func(n *ast.Node, v *ast.NodeVisitor) *ast.Node {
599+
if ast.IsJSDocSingleCommentNodeComment(n) {
600+
return n
601+
}
602+
return visitNode(n, v)
603+
}
604+
}
605+
606+
if visitNodes != nil {
607+
wrappedVisitNodes = func(n *ast.NodeList, v *ast.NodeVisitor) *ast.NodeList {
608+
if ast.IsJSDocSingleCommentNodeList(n) {
609+
return n
610+
}
611+
return visitNodes(n, v)
612+
}
613+
}
614+
600615
return ast.NewNodeVisitor(core.Identity, nil, ast.NodeVisitorHooks{
601-
VisitNode: visitNode,
602-
VisitToken: visitNode,
603-
VisitNodes: visitNodes,
616+
VisitNode: wrappedVisitNode,
617+
VisitToken: wrappedVisitNode,
618+
VisitNodes: wrappedVisitNodes,
604619
VisitModifiers: func(modifiers *ast.ModifierList, visitor *ast.NodeVisitor) *ast.ModifierList {
605620
if modifiers != nil {
606-
visitNodes(&modifiers.NodeList, visitor)
621+
wrappedVisitNodes(&modifiers.NodeList, visitor)
607622
}
608623
return modifiers
609624
},
610625
})
611626
}
627+
628+
func shouldSkipChild(node *ast.Node) bool {
629+
return node.Kind == ast.KindJSDoc ||
630+
node.Kind == ast.KindJSDocText ||
631+
node.Kind == ast.KindJSDocTypeLiteral ||
632+
node.Kind == ast.KindJSDocSignature ||
633+
ast.IsJSDocLinkLike(node) ||
634+
ast.IsJSDocTag(node)
635+
}

internal/checker/printer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,8 @@ func (c *Checker) formatUnionTypes(types []*Type) []*Type {
372372
}
373373
return result
374374
}
375+
376+
func (c *Checker) TypeToTypeNode(t *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags) *ast.TypeNode {
377+
nodeBuilder := c.getNodeBuilder()
378+
return nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
379+
}

internal/format/api.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func formatNodeLines(ctx context.Context, sourceFile *ast.SourceFile, node *ast.
8080
return nil
8181
}
8282
tokenStart := scanner.GetTokenPosOfNode(node, sourceFile, false)
83-
lineStart := getLineStartPositionForPosition(tokenStart, sourceFile)
83+
lineStart := GetLineStartPositionForPosition(tokenStart, sourceFile)
8484
span := core.NewTextRange(lineStart, node.End())
8585
return FormatSpan(ctx, span, sourceFile, requestKind)
8686
}
@@ -90,7 +90,7 @@ func FormatDocument(ctx context.Context, sourceFile *ast.SourceFile) []core.Text
9090
}
9191

9292
func FormatSelection(ctx context.Context, sourceFile *ast.SourceFile, start int, end int) []core.TextChange {
93-
return FormatSpan(ctx, core.NewTextRange(getLineStartPositionForPosition(start, sourceFile), end), sourceFile, FormatRequestKindFormatSelection)
93+
return FormatSpan(ctx, core.NewTextRange(GetLineStartPositionForPosition(start, sourceFile), end), sourceFile, FormatRequestKindFormatSelection)
9494
}
9595

9696
func FormatOnOpeningCurly(ctx context.Context, sourceFile *ast.SourceFile, position int) []core.TextChange {
@@ -112,7 +112,7 @@ func FormatOnOpeningCurly(ctx context.Context, sourceFile *ast.SourceFile, posit
112112
* ```
113113
* and we wouldn't want to move the closing brace.
114114
*/
115-
textRange := core.NewTextRange(getLineStartPositionForPosition(scanner.GetTokenPosOfNode(outermostNode, sourceFile, false), sourceFile), position)
115+
textRange := core.NewTextRange(GetLineStartPositionForPosition(scanner.GetTokenPosOfNode(outermostNode, sourceFile, false), sourceFile), position)
116116
return FormatSpan(ctx, textRange, sourceFile, FormatRequestKindFormatOnOpeningCurlyBrace)
117117
}
118118

internal/format/span.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ func (w *formatSpanWorker) processChildNodes(
467467
// }: {};
468468
indentationOnListStartToken = w.indentationOnLastIndentedLine
469469
} else {
470-
startLinePosition := getLineStartPositionForPosition(tokenInfo.token.Loc.Pos(), w.sourceFile)
470+
startLinePosition := GetLineStartPositionForPosition(tokenInfo.token.Loc.Pos(), w.sourceFile)
471471
indentationOnListStartToken = findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.Loc.Pos(), w.sourceFile, w.formattingContext.Options)
472472
}
473473

@@ -577,7 +577,7 @@ func (w *formatSpanWorker) tryComputeIndentationForListItem(startPos int, endPos
577577
}
578578
} else {
579579
startLine, _ := scanner.GetLineAndCharacterOfPosition(w.sourceFile, startPos)
580-
startLinePosition := getLineStartPositionForPosition(startPos, w.sourceFile)
580+
startLinePosition := GetLineStartPositionForPosition(startPos, w.sourceFile)
581581
column := findFirstNonWhitespaceColumn(startLinePosition, startPos, w.sourceFile, w.formattingContext.Options)
582582
if startLine != parentStartLine || startPos == column {
583583
// Use the base indent size if it is greater than

internal/format/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func getCloseTokenForOpenToken(kind ast.Kind) ast.Kind {
7575
return ast.KindUnknown
7676
}
7777

78-
func getLineStartPositionForPosition(position int, sourceFile *ast.SourceFile) int {
78+
func GetLineStartPositionForPosition(position int, sourceFile *ast.SourceFile) int {
7979
lineStarts := scanner.GetLineStarts(sourceFile)
8080
line, _ := scanner.GetLineAndCharacterOfPosition(sourceFile, position)
8181
return int(lineStarts[line])

0 commit comments

Comments
 (0)