@@ -40,6 +40,18 @@ func GetTokenAtPosition(sourceFile *ast.SourceFile, position int) *ast.Node {
4040 return getTokenAtPosition (sourceFile , position , true /*allowPositionInLeadingTrivia*/ , nil )
4141}
4242
43+ func shouldVisitNode (node * ast.Node , allowReparsed bool ) bool {
44+ // Skip reparsed nodes unless:
45+ // 1. The node itself is AsExpression or SatisfiesExpression, OR
46+ // 2. We're already inside an AsExpression or SatisfiesExpression (allowReparsed=true)
47+ // These are special cases where reparsed nodes from JSDoc type assertions
48+ // should still be navigable to reach identifiers.
49+ isSpecialReparsed := node .Flags & ast .NodeFlagsReparsed != 0 &&
50+ (node .Kind == ast .KindAsExpression || node .Kind == ast .KindSatisfiesExpression )
51+
52+ return node .Flags & ast .NodeFlagsReparsed == 0 || isSpecialReparsed || allowReparsed
53+ }
54+
4355func getTokenAtPosition (
4456 sourceFile * ast.SourceFile ,
4557 position int ,
@@ -65,6 +77,9 @@ func getTokenAtPosition(
6577 // `left` tracks the lower boundary of the node/token that could be returned,
6678 // and is eventually the scanner's start position, if the scanner is used.
6779 left := 0
80+ // `nodeAfterLeft` tracks the first node we visit after visiting the node that advances `left`.
81+ // When scanning in between nodes for token, we should only scan up to the start of `nodeAfterLeft`.
82+ var nodeAfterLeft * ast.Node
6883 // `allowReparsed` is set when we're navigating inside an AsExpression or
6984 // SatisfiesExpression, which allows visiting their reparsed children to reach
7085 // the actual identifier from JSDoc type assertions.
@@ -91,16 +106,14 @@ func getTokenAtPosition(
91106 visitNode := func (node * ast.Node , _ * ast.NodeVisitor ) * ast.Node {
92107 // We can't abort visiting children, so once a match is found, we set `next`
93108 // and do nothing on subsequent visits.
94- if node != nil && next == nil {
95- // Skip reparsed nodes unless:
96- // 1. The node itself is AsExpression or SatisfiesExpression, OR
97- // 2. We're already inside an AsExpression or SatisfiesExpression (allowReparsed=true)
98- // These are special cases where reparsed nodes from JSDoc type assertions
99- // should still be navigable to reach identifiers.
100- isSpecialReparsed := node .Flags & ast .NodeFlagsReparsed != 0 &&
101- (node .Kind == ast .KindAsExpression || node .Kind == ast .KindSatisfiesExpression )
102-
103- if node .Flags & ast .NodeFlagsReparsed == 0 || isSpecialReparsed || allowReparsed {
109+ if node == nil {
110+ return nil
111+ }
112+ if nodeAfterLeft == nil {
113+ nodeAfterLeft = node
114+ }
115+ if next == nil {
116+ if shouldVisitNode (node , allowReparsed ) {
104117 result := testNode (node )
105118 switch result {
106119 case - 1 :
@@ -111,6 +124,7 @@ func getTokenAtPosition(
111124 // all its leading trivia in its position.
112125 left = node .End ()
113126 }
127+ nodeAfterLeft = nil
114128 case 0 :
115129 next = node
116130 }
@@ -120,12 +134,25 @@ func getTokenAtPosition(
120134 }
121135
122136 visitNodeList := func (nodeList * ast.NodeList , _ * ast.NodeVisitor ) * ast.NodeList {
123- if nodeList != nil && len (nodeList .Nodes ) > 0 && next == nil {
137+ if nodeList == nil || len (nodeList .Nodes ) == 0 {
138+ return nodeList
139+ }
140+ if nodeAfterLeft == nil {
141+ for _ , node := range nodeList .Nodes {
142+ if shouldVisitNode (node , allowReparsed ) {
143+ nodeAfterLeft = node
144+ break
145+ }
146+ }
147+ }
148+ if next == nil {
124149 if nodeList .End () == position && includePrecedingTokenAtEndPosition != nil {
125150 left = nodeList .End ()
151+ nodeAfterLeft = nil
126152 prevSubtree = nodeList .Nodes [len (nodeList .Nodes )- 1 ]
127153 } else if nodeList .End () <= position {
128154 left = nodeList .End ()
155+ nodeAfterLeft = nil
129156 } else if nodeList .Pos () <= position {
130157 nodes := nodeList .Nodes
131158 index , match := core .BinarySearchUniqueFunc (nodes , func (middle int , node * ast.Node ) int {
@@ -135,6 +162,13 @@ func getTokenAtPosition(
135162 cmp := testNode (node )
136163 if cmp < 0 {
137164 left = node .End ()
165+ nodeAfterLeft = nil
166+ for i := middle + 1 ; i < len (nodes ); i ++ {
167+ if shouldVisitNode (nodes [i ], allowReparsed ) {
168+ nodeAfterLeft = nodes [i ]
169+ break
170+ }
171+ }
138172 }
139173 return cmp
140174 })
@@ -147,6 +181,11 @@ func getTokenAtPosition(
147181 cmp := testNode (node )
148182 if cmp < 0 {
149183 left = node .End ()
184+ if middle + 1 < len (nodes ) {
185+ nodeAfterLeft = nodes [middle + 1 ]
186+ } else {
187+ nodeAfterLeft = nil
188+ }
150189 }
151190 return cmp
152191 })
@@ -182,12 +221,33 @@ func getTokenAtPosition(
182221 return current
183222 }
184223 scanner := scanner .GetScannerForSourceFile (sourceFile , left )
185- for left < current .End () {
224+ end := current .End ()
225+ // We should only scan up to the start of the next node in the AST after the node ending at position `left`.
226+ // It is necessary to enforce this invariant in cases where `position` occurs in between two node/tokens,
227+ // such that we would not find a token in the loop below before we reach the next node.
228+ // We can fall into this case when `allowPositionInLeadingTrivia` is false and `position` is in a leading trivia,
229+ // or when `position` would be in the leading trivia of a node but this node is inside JSDoc:
230+ // ```
231+ // /**
232+ // * @type {{
233+ // */*$*/ identifier: boolean;
234+ // * }}
235+ // */
236+ // ```
237+ // The position of marker '$' falls in between the asterisk token and the identifier token, but is not
238+ // part of the leading trivia for `identifier`.
239+ if nodeAfterLeft != nil {
240+ end = nodeAfterLeft .Pos ()
241+ }
242+ for left < end {
186243 token := scanner .Token ()
187244 tokenFullStart := scanner .TokenFullStart ()
188245 tokenStart := core .IfElse (allowPositionInLeadingTrivia , tokenFullStart , scanner .TokenStart ())
189246 tokenEnd := scanner .TokenEnd ()
190247 flags := scanner .TokenFlags ()
248+ if tokenEnd > end {
249+ break
250+ }
191251 if tokenStart <= position && (position < tokenEnd ) {
192252 if token == ast .KindIdentifier || ! ast .IsTokenKind (token ) {
193253 if ast .IsJSDocKind (current .Kind ) {
@@ -210,6 +270,7 @@ func getTokenAtPosition(
210270 }
211271 current = next
212272 left = current .Pos ()
273+ nodeAfterLeft = nil
213274 next = nil
214275 // When navigating into AsExpression or SatisfiesExpression, allow visiting
215276 // their reparsed children to reach identifiers from JSDoc type assertions.
0 commit comments