@@ -246,3 +246,286 @@ func visitEachChildAndJSDoc(node *ast.Node, sourceFile *ast.SourceFile, visitor
246
246
}
247
247
node .VisitEachChild (visitor )
248
248
}
249
+
250
+ const (
251
+ comparisonLessThan = - 1
252
+ comparisonEqualTo = 0
253
+ comparisonGreaterThan = 1
254
+ )
255
+
256
+ // Finds the leftmost token satisfying `position < token.End()`.
257
+ // If the leftmost token satisfying `position < token.End()` is invalid, or if position
258
+ // is in the trivia of that leftmost token,
259
+ // we will find the rightmost valid token with `token.End() <= position`.
260
+ func FindPrecedingToken (sourceFile * ast.SourceFile , position int ) * ast.Node {
261
+ return FindPrecedingTokenEx (sourceFile , position , nil , false )
262
+ }
263
+
264
+ func FindPrecedingTokenEx (sourceFile * ast.SourceFile , position int , startNode * ast.Node , excludeJSDoc bool ) * ast.Node {
265
+ var find func (node * ast.Node ) * ast.Node
266
+ find = func (n * ast.Node ) * ast.Node {
267
+ if ast .IsNonWhitespaceToken (n ) {
268
+ return n
269
+ }
270
+
271
+ // `foundChild` is the leftmost node that contains the target position.
272
+ // `prevChild` is the last visited child of the current node.
273
+ var foundChild , prevChild * ast.Node
274
+ visitNode := func (node * ast.Node , _ * ast.NodeVisitor ) * ast.Node {
275
+ // skip synthesized nodes (that will exist now because of jsdoc handling)
276
+ if node == nil || node .Flags & ast .NodeFlagsReparsed != 0 {
277
+ return node
278
+ }
279
+ if foundChild != nil { // We cannot abort visiting children, so once the desired child is found, we do nothing.
280
+ return node
281
+ }
282
+ if position < node .End () && (prevChild == nil || prevChild .End () <= position ) {
283
+ foundChild = node
284
+ } else {
285
+ prevChild = node
286
+ }
287
+ return node
288
+ }
289
+ visitNodes := func (nodeList * ast.NodeList , _ * ast.NodeVisitor ) * ast.NodeList {
290
+ if foundChild != nil {
291
+ return nodeList
292
+ }
293
+ if nodeList != nil && len (nodeList .Nodes ) > 0 {
294
+ nodes := nodeList .Nodes
295
+ if isJSDocSingleCommentNodeList (n , nodeList ) {
296
+ return nodeList
297
+ }
298
+ index , match := core .BinarySearchUniqueFunc (nodes , func (middle int , _ * ast.Node ) int {
299
+ // synthetic jsdoc nodes should have jsdocNode.End() <= n.Pos()
300
+ if nodes [middle ].Flags & ast .NodeFlagsReparsed != 0 {
301
+ return comparisonLessThan
302
+ }
303
+ if position < nodes [middle ].End () {
304
+ if middle == 0 || position >= nodes [middle - 1 ].End () {
305
+ return comparisonEqualTo
306
+ }
307
+ return comparisonGreaterThan
308
+ }
309
+ return comparisonLessThan
310
+ })
311
+
312
+ if match {
313
+ foundChild = nodes [index ]
314
+ }
315
+
316
+ validLookupIndex := core .IfElse (match , index - 1 , len (nodes )- 1 )
317
+ for i := validLookupIndex ; i >= 0 ; i -- {
318
+ if nodes [i ].Flags & ast .NodeFlagsReparsed != 0 {
319
+ continue
320
+ }
321
+ if prevChild == nil {
322
+ prevChild = nodes [i ]
323
+ }
324
+ }
325
+ }
326
+ return nodeList
327
+ }
328
+ nodeVisitor := ast .NewNodeVisitor (core .Identity , nil , ast.NodeVisitorHooks {
329
+ VisitNode : visitNode ,
330
+ VisitToken : visitNode ,
331
+ VisitNodes : visitNodes ,
332
+ VisitModifiers : func (modifiers * ast.ModifierList , visitor * ast.NodeVisitor ) * ast.ModifierList {
333
+ if modifiers != nil {
334
+ visitNodes (& modifiers .NodeList , visitor )
335
+ }
336
+ return modifiers
337
+ },
338
+ })
339
+ visitEachChildAndJSDoc (n , sourceFile , nodeVisitor )
340
+
341
+ if foundChild != nil {
342
+ // Note that the span of a node's tokens is [getStartOfNode(node, ...), node.end).
343
+ // Given that `position < child.end` and child has constituent tokens, we distinguish these cases:
344
+ // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`):
345
+ // we need to find the last token in a previous child node or child tokens.
346
+ // 2) `position` is within the same span: we recurse on `child`.
347
+ start := getStartOfNode (foundChild , sourceFile , ! excludeJSDoc /*includeJSDoc*/ )
348
+ lookInPreviousChild := start >= position || // cursor in the leading trivia or preceding tokens
349
+ ! isValidPrecedingNode (foundChild , sourceFile )
350
+ if lookInPreviousChild {
351
+ if position >= foundChild .Pos () {
352
+ // Find jsdoc preceding the foundChild.
353
+ var jsDoc * ast.Node
354
+ nodeJSDoc := n .JSDoc (sourceFile )
355
+ for i := len (nodeJSDoc ) - 1 ; i >= 0 ; i -- {
356
+ if nodeJSDoc [i ].Pos () >= foundChild .Pos () {
357
+ jsDoc = nodeJSDoc [i ]
358
+ break
359
+ }
360
+ }
361
+ if jsDoc != nil {
362
+ if ! excludeJSDoc {
363
+ return find (jsDoc )
364
+ } else {
365
+ return findRightmostValidToken (jsDoc .End (), sourceFile , n , position , excludeJSDoc )
366
+ }
367
+ }
368
+ return findRightmostValidToken (foundChild .Pos (), sourceFile , n , - 1 /*position*/ , excludeJSDoc )
369
+ } else { // Answer is in tokens between two visited children.
370
+ return findRightmostValidToken (foundChild .Pos (), sourceFile , n , position , excludeJSDoc )
371
+ }
372
+ } else {
373
+ // position is in [foundChild.getStart(), foundChild.End): recur.
374
+ return find (foundChild )
375
+ }
376
+ }
377
+
378
+ // We have two cases here: either the position is at the end of the file,
379
+ // or the desired token is in the unvisited trailing tokens of the current node.
380
+ if position >= n .End () {
381
+ return findRightmostValidToken (n .End (), sourceFile , n , - 1 /*position*/ , excludeJSDoc )
382
+ } else {
383
+ return findRightmostValidToken (n .End (), sourceFile , n , position , excludeJSDoc )
384
+ }
385
+ }
386
+
387
+ var node * ast.Node
388
+ if startNode != nil {
389
+ node = startNode
390
+ } else {
391
+ node = sourceFile .AsNode ()
392
+ }
393
+ result := find (node )
394
+ if result != nil && ast .IsWhitespaceOnlyJsxText (result ) {
395
+ panic ("Expected result to be a non-whitespace token." )
396
+ }
397
+ return result
398
+ }
399
+
400
+ func isValidPrecedingNode (node * ast.Node , sourceFile * ast.SourceFile ) bool {
401
+ start := getStartOfNode (node , sourceFile , false /*includeJSDoc*/ )
402
+ width := node .End () - start
403
+ return ! (ast .IsWhitespaceOnlyJsxText (node ) || width == 0 )
404
+ }
405
+
406
+ func getStartOfNode (node * ast.Node , file * ast.SourceFile , includeJSDoc bool ) int {
407
+ return scanner .GetTokenPosOfNode (node , file , includeJSDoc )
408
+ }
409
+
410
+ // If this is a single comment JSDoc, we do not visit the comment node.
411
+ func isJSDocSingleCommentNodeList (parent * ast.Node , nodeList * ast.NodeList ) bool {
412
+ return parent .Kind == ast .KindJSDoc && nodeList == parent .AsJSDoc ().Comment && nodeList != nil && len (nodeList .Nodes ) == 1
413
+ }
414
+
415
+ // Looks for rightmost valid token in the range [startPos, endPos).
416
+ // If position is >= 0, looks for rightmost valid token that precedes or touches that position.
417
+ func findRightmostValidToken (endPos int , sourceFile * ast.SourceFile , containingNode * ast.Node , position int , excludeJSDoc bool ) * ast.Node {
418
+ if position == - 1 {
419
+ position = containingNode .End ()
420
+ }
421
+ var find func (n * ast.Node ) * ast.Node
422
+ find = func (n * ast.Node ) * ast.Node {
423
+ if n == nil {
424
+ return nil
425
+ }
426
+ if ast .IsNonWhitespaceToken (n ) {
427
+ return n
428
+ }
429
+
430
+ var rightmostValidNode * ast.Node
431
+ var rightmostVisitedNode * ast.Node
432
+ hasChildren := false
433
+ test := func (node * ast.Node ) bool {
434
+ if node .Flags & ast .NodeFlagsReparsed != 0 ||
435
+ node .End () > endPos || getStartOfNode (node , sourceFile , ! excludeJSDoc /*includeJSDoc*/ ) >= position {
436
+ return false
437
+ }
438
+ rightmostVisitedNode = node
439
+ if isValidPrecedingNode (node , sourceFile ) {
440
+ rightmostValidNode = node
441
+ return true
442
+ }
443
+ return false
444
+ }
445
+ visitNode := func (node * ast.Node , _ * ast.NodeVisitor ) * ast.Node {
446
+ if node == nil {
447
+ return node
448
+ }
449
+ hasChildren = true
450
+ test (node )
451
+ return node
452
+ }
453
+ visitNodes := func (nodeList * ast.NodeList , _ * ast.NodeVisitor ) * ast.NodeList {
454
+ if nodeList != nil && len (nodeList .Nodes ) > 0 {
455
+ if isJSDocSingleCommentNodeList (n , nodeList ) {
456
+ return nodeList
457
+ }
458
+ hasChildren = true
459
+ index , _ := core .BinarySearchUniqueFunc (nodeList .Nodes , func (middle int , node * ast.Node ) int {
460
+ if node .End () > endPos {
461
+ return comparisonGreaterThan
462
+ }
463
+ return comparisonLessThan
464
+ })
465
+ for i := index - 1 ; i >= 0 ; i -- {
466
+ if test (nodeList .Nodes [i ]) {
467
+ break
468
+ }
469
+ }
470
+ }
471
+ return nodeList
472
+ }
473
+ nodeVisitor := ast .NewNodeVisitor (core .Identity , nil , ast.NodeVisitorHooks {
474
+ VisitNode : visitNode ,
475
+ VisitToken : visitNode ,
476
+ VisitNodes : visitNodes ,
477
+ VisitModifiers : func (modifiers * ast.ModifierList , visitor * ast.NodeVisitor ) * ast.ModifierList {
478
+ if modifiers != nil {
479
+ visitNodes (& modifiers .NodeList , visitor )
480
+ }
481
+ return modifiers
482
+ },
483
+ })
484
+ visitEachChildAndJSDoc (n , sourceFile , nodeVisitor )
485
+
486
+ // Three cases:
487
+ // 1. The answer is a token of `rightmostValidNode`.
488
+ // 2. The answer is one of the unvisited tokens that occur after the last visited node.
489
+ // 3. The current node is a childless, token-less node. The answer is the current node.
490
+
491
+ // Case 2: Look at trailing tokens.
492
+ if ! ast .IsJSDocCommentContainingNode (n ) { // JSDoc nodes don't include trivia tokens as children.
493
+ var startPos int
494
+ if rightmostVisitedNode != nil {
495
+ startPos = rightmostVisitedNode .End ()
496
+ } else {
497
+ startPos = n .Pos ()
498
+ }
499
+ scanner := scanner .GetScannerForSourceFile (sourceFile , startPos )
500
+ var tokens []* ast.Node
501
+ for startPos < min (endPos , position ) {
502
+ tokenStart := scanner .TokenStart ()
503
+ if tokenStart >= position {
504
+ break
505
+ }
506
+ token := scanner .Token ()
507
+ tokenFullStart := scanner .TokenFullStart ()
508
+ tokenEnd := scanner .TokenEnd ()
509
+ startPos = tokenEnd
510
+ tokens = append (tokens , sourceFile .GetOrCreateToken (token , tokenFullStart , tokenEnd , n ))
511
+ scanner .Scan ()
512
+ }
513
+ lastToken := len (tokens ) - 1
514
+ // Find preceding valid token.
515
+ for i := lastToken ; i >= 0 ; i -- {
516
+ if ! ast .IsWhitespaceOnlyJsxText (tokens [i ]) {
517
+ return tokens [i ]
518
+ }
519
+ }
520
+ }
521
+
522
+ // Case 3: childless node.
523
+ if ! hasChildren {
524
+ return n
525
+ }
526
+ // Case 1: recur on rightmostValidNode.
527
+ return find (rightmostValidNode )
528
+ }
529
+
530
+ return find (containingNode )
531
+ }
0 commit comments