diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 4f7fa227c3f..4316dd2f63f 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -10912,6 +10912,11 @@ type HasFileName interface { Path() tspath.Path } +type TokenCacheKey struct { + parent *Node + loc core.TextRange +} + type SourceFile struct { NodeBase DeclarationBase @@ -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 @@ -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 } diff --git a/internal/astnav/tokens.go b/internal/astnav/tokens.go index fb2eb25c9ab..a0d231dd52b 100644 --- a/internal/astnav/tokens.go +++ b/internal/astnav/tokens.go @@ -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 @@ -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 diff --git a/internal/fourslash/tests/completionJSDocNoCrash_test.go b/internal/fourslash/tests/completionJSDocNoCrash_test.go new file mode 100644 index 00000000000..c4400a5667d --- /dev/null +++ b/internal/fourslash/tests/completionJSDocNoCrash_test.go @@ -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{}, + }) +} diff --git a/internal/ls/lsutil/children.go b/internal/ls/lsutil/children.go index b3ea1eb3557..60f04d4e07f 100644 --- a/internal/ls/lsutil/children.go +++ b/internal/ls/lsutil/children.go @@ -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