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