1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Rules;
4:
5: use PHPStan\ShouldNotHappenException;
6: use function array_map;
7: use function class_exists;
8: use function count;
9: use function implode;
10: use function sprintf;
11:
12: /** @api */
13: class RuleErrorBuilder
14: {
15:
16: private const TYPE_MESSAGE = 1;
17: private const TYPE_LINE = 2;
18: private const TYPE_FILE = 4;
19: private const TYPE_TIP = 8;
20: private const TYPE_IDENTIFIER = 16;
21: private const TYPE_METADATA = 32;
22: private const TYPE_NON_IGNORABLE = 64;
23:
24: private int $type;
25:
26: /** @var mixed[] */
27: private array $properties;
28:
29: /** @var list<string> */
30: private array $tips = [];
31:
32: private function __construct(string $message)
33: {
34: $this->properties['message'] = $message;
35: $this->type = self::TYPE_MESSAGE;
36: }
37:
38: /**
39: * @return array<int, array{string, string|null, string|null, string|null}>
40: */
41: public static function getRuleErrorTypes(): array
42: {
43: return [
44: self::TYPE_MESSAGE => [
45: RuleError::class,
46: 'message',
47: 'string',
48: 'string',
49: ],
50: self::TYPE_LINE => [
51: LineRuleError::class,
52: 'line',
53: 'int',
54: 'int',
55: ],
56: self::TYPE_FILE => [
57: FileRuleError::class,
58: 'file',
59: 'string',
60: 'string',
61: ],
62: self::TYPE_TIP => [
63: TipRuleError::class,
64: 'tip',
65: 'string',
66: 'string',
67: ],
68: self::TYPE_IDENTIFIER => [
69: IdentifierRuleError::class,
70: 'identifier',
71: 'string',
72: 'string',
73: ],
74: self::TYPE_METADATA => [
75: MetadataRuleError::class,
76: 'metadata',
77: 'array',
78: 'mixed[]',
79: ],
80: self::TYPE_NON_IGNORABLE => [
81: NonIgnorableRuleError::class,
82: null,
83: null,
84: null,
85: ],
86: ];
87: }
88:
89: public static function message(string $message): self
90: {
91: return new self($message);
92: }
93:
94: public function line(int $line): self
95: {
96: $this->properties['line'] = $line;
97: $this->type |= self::TYPE_LINE;
98:
99: return $this;
100: }
101:
102: public function file(string $file): self
103: {
104: $this->properties['file'] = $file;
105: $this->type |= self::TYPE_FILE;
106:
107: return $this;
108: }
109:
110: public function tip(string $tip): self
111: {
112: $this->tips = [$tip];
113: $this->type |= self::TYPE_TIP;
114:
115: return $this;
116: }
117:
118: public function addTip(string $tip): self
119: {
120: $this->tips[] = $tip;
121: $this->type |= self::TYPE_TIP;
122:
123: return $this;
124: }
125:
126: public function discoveringSymbolsTip(): self
127: {
128: return $this->tip('Learn more at https://phpstan.org/user-guide/discovering-symbols');
129: }
130:
131: /**
132: * @param list<string> $reasons
133: */
134: public function acceptsReasonsTip(array $reasons): self
135: {
136: foreach ($reasons as $reason) {
137: $this->addTip($reason);
138: }
139:
140: return $this;
141: }
142:
143: public function identifier(string $identifier): self
144: {
145: $this->properties['identifier'] = $identifier;
146: $this->type |= self::TYPE_IDENTIFIER;
147:
148: return $this;
149: }
150:
151: /**
152: * @param mixed[] $metadata
153: */
154: public function metadata(array $metadata): self
155: {
156: $this->properties['metadata'] = $metadata;
157: $this->type |= self::TYPE_METADATA;
158:
159: return $this;
160: }
161:
162: public function nonIgnorable(): self
163: {
164: $this->type |= self::TYPE_NON_IGNORABLE;
165:
166: return $this;
167: }
168:
169: public function build(): RuleError
170: {
171: /** @var class-string<RuleError> $className */
172: $className = sprintf('PHPStan\\Rules\\RuleErrors\\RuleError%d', $this->type);
173: if (!class_exists($className)) {
174: throw new ShouldNotHappenException(sprintf('Class %s does not exist.', $className));
175: }
176:
177: $ruleError = new $className();
178: foreach ($this->properties as $propertyName => $value) {
179: $ruleError->{$propertyName} = $value;
180: }
181:
182: if (count($this->tips) > 0) {
183: if (count($this->tips) === 1) {
184: $ruleError->tip = $this->tips[0];
185: } else {
186: $ruleError->tip = implode("\n", array_map(static fn (string $tip) => sprintf('• %s', $tip), $this->tips));
187: }
188: }
189:
190: return $ruleError;
191: }
192:
193: }
194: