Skip to content
Merged
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
17 changes: 10 additions & 7 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -10912,6 +10912,11 @@ type HasFileName interface {
Path() tspath.Path
}

type TokenCacheKey struct {
parent *Node
loc core.TextRange
}

type SourceFile struct {
NodeBase
DeclarationBase
Expand Down Expand Up @@ -10973,7 +10978,7 @@ type SourceFile struct {

Hash xxh3.Uint128
tokenCacheMu sync.Mutex
tokenCache map[core.TextRange]*Node
tokenCache map[TokenCacheKey]*Node
tokenFactory *NodeFactory
declarationMapMu sync.Mutex
declarationMap map[string][]*Node
Expand Down Expand Up @@ -11178,25 +11183,23 @@ func (node *SourceFile) GetOrCreateToken(
node.tokenCacheMu.Lock()
defer node.tokenCacheMu.Unlock()
loc := core.NewTextRange(pos, end)
if token, ok := node.tokenCache[loc]; ok {
key := TokenCacheKey{parent, loc}
if token, ok := node.tokenCache[key]; ok {
if token.Kind != kind {
panic(fmt.Sprintf("Token cache mismatch: %v != %v", token.Kind, kind))
}
if token.Parent != parent {
panic(fmt.Sprintf("Token cache mismatch: parent. Expected parent of kind %v, got %v", token.Parent.Kind, parent.Kind))
}
return token
}
if parent.Flags&NodeFlagsReparsed != 0 {
panic(fmt.Sprintf("Cannot create token from reparsed node of kind %v", parent.Kind))
}
if node.tokenCache == nil {
node.tokenCache = make(map[core.TextRange]*Node)
node.tokenCache = make(map[TokenCacheKey]*Node)
}
token := createToken(kind, node, pos, end, flags)
token.Loc = loc
token.Parent = parent
node.tokenCache[loc] = token
node.tokenCache[key] = token
return token
}

Expand Down
30 changes: 14 additions & 16 deletions internal/astnav/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,26 @@ func getTokenAtPosition(
visitNode := func(node *ast.Node, _ *ast.NodeVisitor) *ast.Node {
// We can't abort visiting children, so once a match is found, we set `next`
// and do nothing on subsequent visits.
if node == nil {
if node == nil || node.Flags&ast.NodeFlagsReparsed != 0 {
return nil
}
if nodeAfterLeft == nil {
nodeAfterLeft = node
}
if next == nil {
if node.Flags&ast.NodeFlagsReparsed == 0 {
result := testNode(node)
switch result {
case -1:
if !ast.IsJSDocKind(node.Kind) {
// We can't move the left boundary into or beyond JSDoc,
// because we may end up returning the token after this JSDoc,
// constructing it with the scanner, and we need to include
// all its leading trivia in its position.
left = node.End()
}
nodeAfterLeft = nil
case 0:
next = node
result := testNode(node)
switch result {
case -1:
if !ast.IsJSDocKind(node.Kind) {
// We can't move the left boundary into or beyond JSDoc,
// because we may end up returning the token after this JSDoc,
// constructing it with the scanner, and we need to include
// all its leading trivia in its position.
left = node.End()
}
nodeAfterLeft = nil
case 0:
next = node
}
}
return node
Expand Down Expand Up @@ -469,7 +467,7 @@ func findRightmostValidToken(endPos int, sourceFile *ast.SourceFile, containingN
node.End() > endPos || GetStartOfNode(node, sourceFile, !excludeJSDoc /*includeJSDoc*/) >= position)
}
visitNode := func(node *ast.Node, _ *ast.NodeVisitor) *ast.Node {
if node == nil {
if node == nil || node.Flags&ast.NodeFlagsReparsed != 0 {
return node
}
hasChildren = true
Expand Down
32 changes: 32 additions & 0 deletions internal/fourslash/tests/completionJSDocNoCrash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package fourslash_test

import (
"testing"

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

func TestCompletionJSDocNoCrash(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `
// @allowJs: true
// @filename: file.js
class ErrorMap {
/**
* @type {string}
*//*1*/
errorMap;
}
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()

// The assertion here is simply "does not crash/panic".
f.VerifyCompletions(t, "1", &fourslash.CompletionsExpectedList{
IsIncomplete: false,
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{CommitCharacters: &[]string{".", ",", ";"}},
Items: &fourslash.CompletionsExpectedItems{},
})
}
2 changes: 1 addition & 1 deletion internal/ls/lsutil/children.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func GetLastVisitedChild(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
var lastChild *ast.Node

visitNode := func(n *ast.Node, _ *ast.NodeVisitor) *ast.Node {
if !(n == nil || node.Flags&ast.NodeFlagsReparsed != 0) {
if n != nil && n.Flags&ast.NodeFlagsReparsed == 0 {
lastChild = n
}
return n
Expand Down