xref: /PHP-Parser/lib/PhpParser/Error.php (revision 09691fc8)
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