CommentAnnotatingVisitor.php 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. <?php declare(strict_types=1);
  2. namespace PhpParser\NodeVisitor;
  3. use PhpParser\Comment;
  4. use PhpParser\Node;
  5. use PhpParser\NodeVisitorAbstract;
  6. use PhpParser\Token;
  7. class CommentAnnotatingVisitor extends NodeVisitorAbstract {
  8. /** @var int Last seen token start position */
  9. private int $pos = 0;
  10. /** @var Token[] Token array */
  11. private array $tokens;
  12. /** @var list<int> Token positions of comments */
  13. private array $commentPositions = [];
  14. /**
  15. * Create a comment annotation visitor.
  16. *
  17. * @param Token[] $tokens Token array
  18. */
  19. public function __construct(array $tokens) {
  20. $this->tokens = $tokens;
  21. // Collect positions of comments. We use this to avoid traversing parts of the AST where
  22. // there are no comments.
  23. foreach ($tokens as $i => $token) {
  24. if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
  25. $this->commentPositions[] = $i;
  26. }
  27. }
  28. }
  29. public function enterNode(Node $node) {
  30. $nextCommentPos = current($this->commentPositions);
  31. if ($nextCommentPos === false) {
  32. // No more comments.
  33. return self::STOP_TRAVERSAL;
  34. }
  35. $oldPos = $this->pos;
  36. $this->pos = $pos = $node->getStartTokenPos();
  37. if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) {
  38. $comments = [];
  39. while (--$pos >= $oldPos) {
  40. $token = $this->tokens[$pos];
  41. if ($token->id === \T_DOC_COMMENT) {
  42. $comments[] = new Comment\Doc(
  43. $token->text, $token->line, $token->pos, $pos,
  44. $token->getEndLine(), $token->getEndPos() - 1, $pos);
  45. continue;
  46. }
  47. if ($token->id === \T_COMMENT) {
  48. $comments[] = new Comment(
  49. $token->text, $token->line, $token->pos, $pos,
  50. $token->getEndLine(), $token->getEndPos() - 1, $pos);
  51. continue;
  52. }
  53. if ($token->id !== \T_WHITESPACE) {
  54. break;
  55. }
  56. }
  57. if (!empty($comments)) {
  58. $node->setAttribute('comments', array_reverse($comments));
  59. }
  60. do {
  61. $nextCommentPos = next($this->commentPositions);
  62. } while ($nextCommentPos !== false && $nextCommentPos < $this->pos);
  63. }
  64. $endPos = $node->getEndTokenPos();
  65. if ($nextCommentPos > $endPos) {
  66. // Skip children if there are no comments located inside this node.
  67. $this->pos = $endPos;
  68. return self::DONT_TRAVERSE_CHILDREN;
  69. }
  70. return null;
  71. }
  72. }