1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser;
4:
5: use Exception;
6: use JsonSerializable;
7: use PhpParser\Node;
8: use PHPStan\ShouldNotHappenException;
9: use ReturnTypeWillChange;
10: use Throwable;
11: use function is_bool;
12:
13: /** @api */
14: class Error implements JsonSerializable
15: {
16:
17: /**
18: * Error constructor.
19: *
20: * @param class-string<Node>|null $nodeType
21: * @param mixed[] $metadata
22: */
23: public function __construct(
24: private string $message,
25: private string $file,
26: private ?int $line = null,
27: private bool|Throwable $canBeIgnored = true,
28: private ?string $filePath = null,
29: private ?string $traitFilePath = null,
30: private ?string $tip = null,
31: private ?int $nodeLine = null,
32: private ?string $nodeType = null,
33: private ?string $identifier = null,
34: private array $metadata = [],
35: )
36: {
37: }
38:
39: public function getMessage(): string
40: {
41: return $this->message;
42: }
43:
44: public function getFile(): string
45: {
46: return $this->file;
47: }
48:
49: public function getFilePath(): string
50: {
51: if ($this->filePath === null) {
52: return $this->file;
53: }
54:
55: return $this->filePath;
56: }
57:
58: public function changeFilePath(string $newFilePath): self
59: {
60: if ($this->traitFilePath !== null) {
61: throw new ShouldNotHappenException('Errors in traits not yet supported');
62: }
63:
64: return new self(
65: $this->message,
66: $newFilePath,
67: $this->line,
68: $this->canBeIgnored,
69: $newFilePath,
70: null,
71: $this->tip,
72: $this->nodeLine,
73: $this->nodeType,
74: $this->identifier,
75: $this->metadata,
76: );
77: }
78:
79: public function changeTraitFilePath(string $newFilePath): self
80: {
81: return new self(
82: $this->message,
83: $this->file,
84: $this->line,
85: $this->canBeIgnored,
86: $this->filePath,
87: $newFilePath,
88: $this->tip,
89: $this->nodeLine,
90: $this->nodeType,
91: $this->identifier,
92: $this->metadata,
93: );
94: }
95:
96: public function getTraitFilePath(): ?string
97: {
98: return $this->traitFilePath;
99: }
100:
101: public function getLine(): ?int
102: {
103: return $this->line;
104: }
105:
106: public function canBeIgnored(): bool
107: {
108: return $this->canBeIgnored === true;
109: }
110:
111: public function hasNonIgnorableException(): bool
112: {
113: return $this->canBeIgnored instanceof Throwable;
114: }
115:
116: public function getTip(): ?string
117: {
118: return $this->tip;
119: }
120:
121: public function withoutTip(): self
122: {
123: if ($this->tip === null) {
124: return $this;
125: }
126:
127: return new self(
128: $this->message,
129: $this->file,
130: $this->line,
131: $this->canBeIgnored,
132: $this->filePath,
133: $this->traitFilePath,
134: null,
135: $this->nodeLine,
136: $this->nodeType,
137: );
138: }
139:
140: public function doNotIgnore(): self
141: {
142: if (!$this->canBeIgnored()) {
143: return $this;
144: }
145:
146: return new self(
147: $this->message,
148: $this->file,
149: $this->line,
150: false,
151: $this->filePath,
152: $this->traitFilePath,
153: $this->tip,
154: $this->nodeLine,
155: $this->nodeType,
156: );
157: }
158:
159: public function getNodeLine(): ?int
160: {
161: return $this->nodeLine;
162: }
163:
164: /**
165: * @return class-string<Node>|null
166: */
167: public function getNodeType(): ?string
168: {
169: return $this->nodeType;
170: }
171:
172: public function getIdentifier(): ?string
173: {
174: return $this->identifier;
175: }
176:
177: /**
178: * @return mixed[]
179: */
180: public function getMetadata(): array
181: {
182: return $this->metadata;
183: }
184:
185: /**
186: * @return mixed
187: */
188: #[ReturnTypeWillChange]
189: public function jsonSerialize()
190: {
191: return [
192: 'message' => $this->message,
193: 'file' => $this->file,
194: 'line' => $this->line,
195: 'canBeIgnored' => is_bool($this->canBeIgnored) ? $this->canBeIgnored : 'exception',
196: 'filePath' => $this->filePath,
197: 'traitFilePath' => $this->traitFilePath,
198: 'tip' => $this->tip,
199: 'nodeLine' => $this->nodeLine,
200: 'nodeType' => $this->nodeType,
201: 'identifier' => $this->identifier,
202: 'metadata' => $this->metadata,
203: ];
204: }
205:
206: /**
207: * @param mixed[] $json
208: */
209: public static function decode(array $json): self
210: {
211: return new self(
212: $json['message'],
213: $json['file'],
214: $json['line'],
215: $json['canBeIgnored'] === 'exception' ? new Exception() : $json['canBeIgnored'],
216: $json['filePath'],
217: $json['traitFilePath'],
218: $json['tip'],
219: $json['nodeLine'] ?? null,
220: $json['nodeType'] ?? null,
221: $json['identifier'] ?? null,
222: $json['metadata'] ?? [],
223: );
224: }
225:
226: /**
227: * @param mixed[] $properties
228: */
229: public static function __set_state(array $properties): self
230: {
231: return new self(
232: $properties['message'],
233: $properties['file'],
234: $properties['line'],
235: $properties['canBeIgnored'],
236: $properties['filePath'],
237: $properties['traitFilePath'],
238: $properties['tip'],
239: $properties['nodeLine'] ?? null,
240: $properties['nodeType'] ?? null,
241: $properties['identifier'] ?? null,
242: $properties['metadata'] ?? [],
243: );
244: }
245:
246: }
247: