Comment.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php declare(strict_types=1);
  2. namespace PhpParser;
  3. class Comment implements \JsonSerializable
  4. {
  5. protected $text;
  6. protected $line;
  7. protected $filePos;
  8. protected $tokenPos;
  9. /**
  10. * Constructs a comment node.
  11. *
  12. * @param string $text Comment text (including comment delimiters like /*)
  13. * @param int $startLine Line number the comment started on
  14. * @param int $startFilePos File offset the comment started on
  15. * @param int $startTokenPos Token offset the comment started on
  16. */
  17. public function __construct(
  18. string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1
  19. ) {
  20. $this->text = $text;
  21. $this->line = $startLine;
  22. $this->filePos = $startFilePos;
  23. $this->tokenPos = $startTokenPos;
  24. }
  25. /**
  26. * Gets the comment text.
  27. *
  28. * @return string The comment text (including comment delimiters like /*)
  29. */
  30. public function getText() : string {
  31. return $this->text;
  32. }
  33. /**
  34. * Gets the line number the comment started on.
  35. *
  36. * @return int Line number
  37. */
  38. public function getLine() : int {
  39. return $this->line;
  40. }
  41. /**
  42. * Gets the file offset the comment started on.
  43. *
  44. * @return int File offset
  45. */
  46. public function getFilePos() : int {
  47. return $this->filePos;
  48. }
  49. /**
  50. * Gets the token offset the comment started on.
  51. *
  52. * @return int Token offset
  53. */
  54. public function getTokenPos() : int {
  55. return $this->tokenPos;
  56. }
  57. /**
  58. * Gets the comment text.
  59. *
  60. * @return string The comment text (including comment delimiters like /*)
  61. */
  62. public function __toString() : string {
  63. return $this->text;
  64. }
  65. /**
  66. * Gets the reformatted comment text.
  67. *
  68. * "Reformatted" here means that we try to clean up the whitespace at the
  69. * starts of the lines. This is necessary because we receive the comments
  70. * without trailing whitespace on the first line, but with trailing whitespace
  71. * on all subsequent lines.
  72. *
  73. * @return mixed|string
  74. */
  75. public function getReformattedText() {
  76. $text = trim($this->text);
  77. $newlinePos = strpos($text, "\n");
  78. if (false === $newlinePos) {
  79. // Single line comments don't need further processing
  80. return $text;
  81. } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
  82. // Multi line comment of the type
  83. //
  84. // /*
  85. // * Some text.
  86. // * Some more text.
  87. // */
  88. //
  89. // is handled by replacing the whitespace sequences before the * by a single space
  90. return preg_replace('(^\s+\*)m', ' *', $this->text);
  91. } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
  92. // Multi line comment of the type
  93. //
  94. // /*
  95. // Some text.
  96. // Some more text.
  97. // */
  98. //
  99. // is handled by removing the whitespace sequence on the line before the closing
  100. // */ on all lines. So if the last line is " */", then " " is removed at the
  101. // start of all lines.
  102. return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
  103. } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
  104. // Multi line comment of the type
  105. //
  106. // /* Some text.
  107. // Some more text.
  108. // Indented text.
  109. // Even more text. */
  110. //
  111. // is handled by removing the difference between the shortest whitespace prefix on all
  112. // lines and the length of the "/* " opening sequence.
  113. $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
  114. $removeLen = $prefixLen - strlen($matches[0]);
  115. return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
  116. }
  117. // No idea how to format this comment, so simply return as is
  118. return $text;
  119. }
  120. /**
  121. * Get length of shortest whitespace prefix (at the start of a line).
  122. *
  123. * If there is a line with no prefix whitespace, 0 is a valid return value.
  124. *
  125. * @param string $str String to check
  126. * @return int Length in characters. Tabs count as single characters.
  127. */
  128. private function getShortestWhitespacePrefixLen(string $str) : int {
  129. $lines = explode("\n", $str);
  130. $shortestPrefixLen = \INF;
  131. foreach ($lines as $line) {
  132. preg_match('(^\s*)', $line, $matches);
  133. $prefixLen = strlen($matches[0]);
  134. if ($prefixLen < $shortestPrefixLen) {
  135. $shortestPrefixLen = $prefixLen;
  136. }
  137. }
  138. return $shortestPrefixLen;
  139. }
  140. /**
  141. * @return array
  142. * @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
  143. */
  144. public function jsonSerialize() : array {
  145. // Technically not a node, but we make it look like one anyway
  146. $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
  147. return [
  148. 'nodeType' => $type,
  149. 'text' => $this->text,
  150. 'line' => $this->line,
  151. 'filePos' => $this->filePos,
  152. 'tokenPos' => $this->tokenPos,
  153. ];
  154. }
  155. }