Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
15 changes: 9 additions & 6 deletions internal/astnav/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ func getTokenAtPosition(
if node.End() < position || node.Kind != ast.KindEndOfFile && node.End() == position {
return -1
}
if getPosition(node, sourceFile, allowPositionInLeadingTrivia) > position {
nodePos := getPosition(node, sourceFile, allowPositionInLeadingTrivia)
if nodePos > position {
return 1
}
return 0
Expand All @@ -87,7 +88,8 @@ func getTokenAtPosition(
// We can't abort visiting children, so once a match is found, we set `next`
// and do nothing on subsequent visits.
if node != nil && node.Flags&ast.NodeFlagsReparsed == 0 && next == nil {
switch testNode(node) {
result := testNode(node)
switch result {
case -1:
if !ast.IsJSDocKind(node.Kind) {
// We can't move the left boundary into or beyond JSDoc,
Expand Down Expand Up @@ -173,10 +175,11 @@ func getTokenAtPosition(
tokenEnd := scanner.TokenEnd()
if tokenStart <= position && (position < tokenEnd) {
if token == ast.KindIdentifier || !ast.IsTokenKind(token) {
if ast.IsJSDocKind(current.Kind) {
return current
}
panic(fmt.Sprintf("did not expect %s to have %s in its trivia", current.Kind.String(), token.String()))
// If we encounter an identifier or complex node while scanning, it means
// the token is part of the current node's structure (even if not properly
// visited as a child). This can happen with JSDoc type assertions and
// other complex expressions. Return the current node as it contains the token.
return current
}
return sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, current)
}
Expand Down
46 changes: 46 additions & 0 deletions internal/astnav/tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,52 @@ func TestGetTokenAtPosition(t *testing.T) {
)
})

t.Run("JSDoc type assertion", func(t *testing.T) {
t.Parallel()
fileText := `function foo(x) {
const s = /**@type {string}*/(x)
}`
file := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.js",
Path: "/test.js",
}, fileText, core.ScriptKindJS)

// Position of 'x' inside the parenthesized expression (position 52)
position := 52

// This should not panic - it previously panicked with:
// "did not expect KindParenthesizedExpression to have KindIdentifier in its trivia"
token := astnav.GetTouchingPropertyName(file, position)
if token == nil {
t.Fatal("Expected to get a token, got nil")
}

// The function may return either the identifier itself or the containing
// parenthesized expression, depending on how the AST is structured
if token.Kind != ast.KindIdentifier && token.Kind != ast.KindParenthesizedExpression {
t.Errorf("Expected identifier or parenthesized expression, got %s", token.Kind)
}
})

t.Run("JSDoc type assertion with comment", func(t *testing.T) {
t.Parallel()
// Exact code from the issue report
fileText := `function foo(x) {
const s = /**@type {string}*/(x) // Go-to-definition on x causes panic
}`
file := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.js",
Path: "/test.js",
}, fileText, core.ScriptKindJS)

// Find position of 'x' in the type assertion
xPos := 52 // Position of 'x' in (x)

// This should not panic
token := astnav.GetTouchingPropertyName(file, xPos)
assert.Assert(t, token != nil, "Expected to get a token")
})

t.Run("pointer equality", func(t *testing.T) {
t.Parallel()
fileText := `
Expand Down
Loading