Error.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <?php declare(strict_types=1);
  2. namespace PhpParser;
  3. class Error extends \RuntimeException {
  4. protected string $rawMessage;
  5. /** @var array<string, mixed> */
  6. protected array $attributes;
  7. /**
  8. * Creates an Exception signifying a parse error.
  9. *
  10. * @param string $message Error message
  11. * @param array<string, mixed> $attributes Attributes of node/token where error occurred
  12. */
  13. public function __construct(string $message, array $attributes = []) {
  14. $this->rawMessage = $message;
  15. $this->attributes = $attributes;
  16. $this->updateMessage();
  17. }
  18. /**
  19. * Gets the error message
  20. *
  21. * @return string Error message
  22. */
  23. public function getRawMessage(): string {
  24. return $this->rawMessage;
  25. }
  26. /**
  27. * Gets the line the error starts in.
  28. *
  29. * @return int Error start line
  30. * @phpstan-return -1|positive-int
  31. */
  32. public function getStartLine(): int {
  33. return $this->attributes['startLine'] ?? -1;
  34. }
  35. /**
  36. * Gets the line the error ends in.
  37. *
  38. * @return int Error end line
  39. * @phpstan-return -1|positive-int
  40. */
  41. public function getEndLine(): int {
  42. return $this->attributes['endLine'] ?? -1;
  43. }
  44. /**
  45. * Gets the attributes of the node/token the error occurred at.
  46. *
  47. * @return array<string, mixed>
  48. */
  49. public function getAttributes(): array {
  50. return $this->attributes;
  51. }
  52. /**
  53. * Sets the attributes of the node/token the error occurred at.
  54. *
  55. * @param array<string, mixed> $attributes
  56. */
  57. public function setAttributes(array $attributes): void {
  58. $this->attributes = $attributes;
  59. $this->updateMessage();
  60. }
  61. /**
  62. * Sets the line of the PHP file the error occurred in.
  63. *
  64. * @param string $message Error message
  65. */
  66. public function setRawMessage(string $message): void {
  67. $this->rawMessage = $message;
  68. $this->updateMessage();
  69. }
  70. /**
  71. * Sets the line the error starts in.
  72. *
  73. * @param int $line Error start line
  74. */
  75. public function setStartLine(int $line): void {
  76. $this->attributes['startLine'] = $line;
  77. $this->updateMessage();
  78. }
  79. /**
  80. * Returns whether the error has start and end column information.
  81. *
  82. * For column information enable the startFilePos and endFilePos in the lexer options.
  83. */
  84. public function hasColumnInfo(): bool {
  85. return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
  86. }
  87. /**
  88. * Gets the start column (1-based) into the line where the error started.
  89. *
  90. * @param string $code Source code of the file
  91. */
  92. public function getStartColumn(string $code): int {
  93. if (!$this->hasColumnInfo()) {
  94. throw new \RuntimeException('Error does not have column information');
  95. }
  96. return $this->toColumn($code, $this->attributes['startFilePos']);
  97. }
  98. /**
  99. * Gets the end column (1-based) into the line where the error ended.
  100. *
  101. * @param string $code Source code of the file
  102. */
  103. public function getEndColumn(string $code): int {
  104. if (!$this->hasColumnInfo()) {
  105. throw new \RuntimeException('Error does not have column information');
  106. }
  107. return $this->toColumn($code, $this->attributes['endFilePos']);
  108. }
  109. /**
  110. * Formats message including line and column information.
  111. *
  112. * @param string $code Source code associated with the error, for calculation of the columns
  113. *
  114. * @return string Formatted message
  115. */
  116. public function getMessageWithColumnInfo(string $code): string {
  117. return sprintf(
  118. '%s from %d:%d to %d:%d', $this->getRawMessage(),
  119. $this->getStartLine(), $this->getStartColumn($code),
  120. $this->getEndLine(), $this->getEndColumn($code)
  121. );
  122. }
  123. /**
  124. * Converts a file offset into a column.
  125. *
  126. * @param string $code Source code that $pos indexes into
  127. * @param int $pos 0-based position in $code
  128. *
  129. * @return int 1-based column (relative to start of line)
  130. */
  131. private function toColumn(string $code, int $pos): int {
  132. if ($pos > strlen($code)) {
  133. throw new \RuntimeException('Invalid position information');
  134. }
  135. $lineStartPos = strrpos($code, "\n", $pos - strlen($code));
  136. if (false === $lineStartPos) {
  137. $lineStartPos = -1;
  138. }
  139. return $pos - $lineStartPos;
  140. }
  141. /**
  142. * Updates the exception message after a change to rawMessage or rawLine.
  143. */
  144. protected function updateMessage(): void {
  145. $this->message = $this->rawMessage;
  146. if (-1 === $this->getStartLine()) {
  147. $this->message .= ' on unknown line';
  148. } else {
  149. $this->message .= ' on line ' . $this->getStartLine();
  150. }
  151. }
  152. }