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