Skip to content

Commit b16ed17

Browse files
authored
Consistently map reparsed JSDoc nodes in GetReparsedNodeForNode (microsoft#2586)
1 parent 463b6b4 commit b16ed17

17 files changed

Lines changed: 200 additions & 111 deletions

internal/ast/ast.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10769,6 +10769,7 @@ type SourceFile struct {
1076910769
AmbientModuleNames []string
1077010770
CommentDirectives []CommentDirective
1077110771
jsdocCache map[*Node][]*Node
10772+
ReparsedClones []*Node
1077210773
Pragmas []Pragma
1077310774
ReferencedFiles []*FileReference
1077410775
TypeReferenceDirectives []*FileReference
@@ -11006,9 +11007,7 @@ func (node *SourceFile) GetOrCreateToken(
1100611007
node.tokenCacheMu.Lock()
1100711008
defer node.tokenCacheMu.Unlock()
1100811009
loc := core.NewTextRange(pos, end)
11009-
if node.tokenCache == nil {
11010-
node.tokenCache = make(map[core.TextRange]*Node)
11011-
} else if token, ok := node.tokenCache[loc]; ok {
11010+
if token, ok := node.tokenCache[loc]; ok {
1101211011
if token.Kind != kind {
1101311012
panic(fmt.Sprintf("Token cache mismatch: %v != %v", token.Kind, kind))
1101411013
}
@@ -11020,6 +11019,9 @@ func (node *SourceFile) GetOrCreateToken(
1102011019
if parent.Flags&NodeFlagsReparsed != 0 {
1102111020
panic(fmt.Sprintf("Cannot create token from reparsed node of kind %v", parent.Kind))
1102211021
}
11022+
if node.tokenCache == nil {
11023+
node.tokenCache = make(map[core.TextRange]*Node)
11024+
}
1102311025
token := createToken(kind, node, pos, end, flags)
1102411026
token.Loc = loc
1102511027
token.Parent = parent

internal/ast/utilities.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3609,14 +3609,14 @@ func IsCallOrNewExpression(node *Node) bool {
36093609
}
36103610

36113611
func IndexOfNode(nodes []*Node, node *Node) int {
3612-
index, ok := slices.BinarySearchFunc(nodes, node, compareNodePositions)
3612+
index, ok := slices.BinarySearchFunc(nodes, node, CompareNodePositions)
36133613
if ok {
36143614
return index
36153615
}
36163616
return -1
36173617
}
36183618

3619-
func compareNodePositions(n1, n2 *Node) int {
3619+
func CompareNodePositions(n1, n2 *Node) int {
36203620
return n1.Pos() - n2.Pos()
36213621
}
36223622

@@ -4264,3 +4264,41 @@ func isArgumentOfElementAccessExpression(node *Node) bool {
42644264
node.Parent.Kind == KindElementAccessExpression &&
42654265
node.Parent.AsElementAccessExpression().ArgumentExpression == node
42664266
}
4267+
4268+
// If the given node is part of a subtree of JSDoc nodes that have been cloned into a reparsed construct,
4269+
// return the corresponding reparsed clone in the subtree. Otherwise, just return the node.
4270+
func GetReparsedNodeForNode(node *Node) *Node {
4271+
if node != nil && node.Flags&NodeFlagsJSDoc != 0 && node.Flags&NodeFlagsReparsed == 0 {
4272+
if file := GetSourceFileOfNode(node); file != nil && len(file.ReparsedClones) != 0 {
4273+
pos, found := slices.BinarySearchFunc(file.ReparsedClones, node, CompareNodePositions)
4274+
if !found && pos > 0 {
4275+
pos--
4276+
}
4277+
candidate := file.ReparsedClones[pos]
4278+
if node.Loc.ContainedBy(candidate.Loc) {
4279+
if reparsed := findCloneInNode(candidate, node); reparsed != nil {
4280+
return reparsed
4281+
}
4282+
}
4283+
}
4284+
}
4285+
return node
4286+
}
4287+
4288+
func findCloneInNode(node *Node, original *Node) *Node {
4289+
for {
4290+
if node.Kind == original.Kind && node.Loc == original.Loc {
4291+
return node
4292+
}
4293+
foundContainingChild := node.ForEachChild(func(n *Node) bool {
4294+
if original.Loc.ContainedBy(n.Loc) {
4295+
node = n
4296+
return true
4297+
}
4298+
return false
4299+
})
4300+
if !foundContainingChild {
4301+
return nil
4302+
}
4303+
}
4304+
}

internal/astnav/tokens.go

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

1111
func GetTouchingPropertyName(sourceFile *ast.SourceFile, position int) *ast.Node {
12-
return getReparsedNodeForNode(getTokenAtPosition(sourceFile, position, false /*allowPositionInLeadingTrivia*/, func(node *ast.Node) bool {
12+
return getTokenAtPosition(sourceFile, position, false /*allowPositionInLeadingTrivia*/, func(node *ast.Node) bool {
1313
return ast.IsPropertyNameLiteral(node) || ast.IsKeywordKind(node.Kind) || ast.IsPrivateIdentifier(node)
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
14+
})
3315
}
3416

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

internal/checker/checker.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11768,7 +11768,7 @@ func (c *Checker) checkThisExpression(node *ast.Node) *Type {
1176811768
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
1176911769
}
1177011770
}
11771-
t := c.TryGetThisTypeAtEx(node, true /*includeGlobalThis*/, container)
11771+
t := c.tryGetThisTypeAtEx(node, true /*includeGlobalThis*/, container)
1177211772
if c.noImplicitThis {
1177311773
globalThisType := c.getTypeOfSymbol(c.globalThisSymbol)
1177411774
if t == globalThisType && capturedByArrowFunction {
@@ -11791,10 +11791,18 @@ func (c *Checker) checkThisExpression(node *ast.Node) *Type {
1179111791
}
1179211792

1179311793
func (c *Checker) tryGetThisTypeAt(node *ast.Node) *Type {
11794-
return c.TryGetThisTypeAtEx(node, true /*includeGlobalThis*/, nil /*container*/)
11794+
return c.tryGetThisTypeAtEx(node, true /*includeGlobalThis*/, nil /*container*/)
1179511795
}
1179611796

1179711797
func (c *Checker) TryGetThisTypeAtEx(node *ast.Node, includeGlobalThis bool, container *ast.Node) *Type {
11798+
reparsed := ast.GetReparsedNodeForNode(node)
11799+
if reparsed.Flags&ast.NodeFlagsJSDoc != 0 && reparsed.Flags&ast.NodeFlagsReparsed == 0 {
11800+
return nil // Binder doesn't process non-reparsed JSDoc nodes
11801+
}
11802+
return c.tryGetThisTypeAtEx(reparsed, includeGlobalThis, ast.GetReparsedNodeForNode(container))
11803+
}
11804+
11805+
func (c *Checker) tryGetThisTypeAtEx(node *ast.Node, includeGlobalThis bool, container *ast.Node) *Type {
1179811806
if container == nil {
1179911807
container = c.getThisContainer(node, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/)
1180011808
}
@@ -30674,7 +30682,7 @@ func (c *Checker) GetSymbolAtLocation(node *ast.Node) *ast.Symbol {
3067430682
if node.Parent == nil || node.Parent.Parent == nil {
3067530683
return nil
3067630684
}
30677-
return c.getSymbolAtLocation(node, true /*ignoreErrors*/)
30685+
return c.getSymbolAtLocation(ast.GetReparsedNodeForNode(node), true /*ignoreErrors*/)
3067830686
}
3067930687

3068030688
// Returns the symbol associated with a given AST node. Do *not* use this function in the checker itself! It should
@@ -31246,7 +31254,7 @@ func (c *Checker) containsArgumentsReference(node *ast.Node) bool {
3124631254
}
3124731255

3124831256
func (c *Checker) GetTypeAtLocation(node *ast.Node) *Type {
31249-
return c.getTypeOfNode(node)
31257+
return c.getTypeOfNode(ast.GetReparsedNodeForNode(node))
3125031258
}
3125131259

3125231260
func (c *Checker) GetEmitResolver() *EmitResolver {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
8+
"github.com/microsoft/typescript-go/internal/testutil"
9+
)
10+
11+
func TestCompletionsJSDocSignature(t *testing.T) {
12+
t.Parallel()
13+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
14+
15+
const content = `// @noLib: true
16+
// @checkJs: true
17+
// @allowJs: true
18+
// @filename: index.js
19+
/**
20+
* @type {{
21+
* (input: string):/*1*/ X|Y/*2*/
22+
* }}
23+
*/
24+
let x;`
25+
26+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
27+
defer done()
28+
f.VerifyCompletions(t, "1", &fourslash.CompletionsExpectedList{
29+
IsIncomplete: false,
30+
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
31+
CommitCharacters: &[]string{".", ",", ";"},
32+
EditRange: Ignored,
33+
},
34+
Items: &fourslash.CompletionsExpectedItems{},
35+
})
36+
f.VerifyCompletions(t, "2", &fourslash.CompletionsExpectedList{
37+
IsIncomplete: false,
38+
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
39+
CommitCharacters: &[]string{".", ",", ";"},
40+
EditRange: Ignored,
41+
},
42+
Items: &fourslash.CompletionsExpectedItems{},
43+
})
44+
}

internal/ls/findallreferences.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ func (l *LanguageService) symbolAndEntriesToRename(ctx context.Context, params *
754754

755755
func (l *LanguageService) getTextForRename(originalNode *ast.Node, entry *ReferenceEntry, newText string, checker *checker.Checker) string {
756756
if entry.kind != entryKindRange && (ast.IsIdentifier(originalNode) || ast.IsStringLiteralLike(originalNode)) {
757-
node := entry.node
757+
node := ast.GetReparsedNodeForNode(entry.node)
758758
kind := entry.kind
759759
parent := node.Parent
760760
name := originalNode.Text()

internal/ls/utilities.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ func symbolFlagsHaveMeaning(flags ast.SymbolFlags, meaning ast.SemanticMeaning)
983983

984984
func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
985985
// todo: check if this function needs to be changed for jsdoc updates
986-
node = getAdjustedLocation(node, false /*forRename*/, nil)
986+
node = getAdjustedLocation(ast.GetReparsedNodeForNode(node), false /*forRename*/, nil)
987987
parent := node.Parent
988988
switch {
989989
case ast.IsSourceFile(node):

internal/parser/parser.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package parser
22

33
import (
4+
"slices"
45
"strings"
56
"sync"
67

@@ -84,6 +85,7 @@ type Parser struct {
8485

8586
currentParent *ast.Node
8687
setParentFromContext ast.Visitor
88+
reparsedClones []*ast.Node
8789
}
8890

8991
func newParser() *Parser {
@@ -382,7 +384,8 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool
382384
result.TextCount = p.factory.TextCount()
383385
result.IdentifierCount = p.identifierCount
384386
result.SetJSDocCache(p.jsdocCache)
385-
387+
slices.SortFunc(p.reparsedClones, ast.CompareNodePositions)
388+
result.ReparsedClones = slices.Clone(p.reparsedClones)
386389
ast.SetExternalModuleIndicator(result, p.opts.ExternalModuleIndicatorOptions)
387390
}
388391

0 commit comments

Comments
 (0)