1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Rules;
4:
5: use PHPStan\Reflection\ClassConstantReflection;
6: use PHPStan\Reflection\ExtendedMethodReflection;
7: use PHPStan\Reflection\ExtendedPropertyReflection;
8: use PHPStan\Reflection\FunctionReflection;
9: use function sprintf;
10: use function ucfirst;
11:
12: /**
13: * @api
14: */
15: final class ClassNameUsageLocation
16: {
17:
18: public const TRAIT_USE = 'traitUse';
19: public const STATIC_PROPERTY_ACCESS = 'staticProperty';
20: public const PHPDOC_TAG_ASSERT = 'assert';
21: public const ATTRIBUTE = 'attribute';
22: public const EXCEPTION_CATCH = 'catch';
23: public const CLASS_CONSTANT_ACCESS = 'classConstant';
24: public const CLASS_IMPLEMENTS = 'classImplements';
25: public const ENUM_IMPLEMENTS = 'enumImplements';
26: public const INTERFACE_EXTENDS = 'interfaceExtends';
27: public const CLASS_EXTENDS = 'classExtends';
28: public const INSTANCEOF = 'instanceof';
29: public const PROPERTY_TYPE = 'property';
30: public const PARAMETER_TYPE = 'parameter';
31: public const RETURN_TYPE = 'return';
32: public const PHPDOC_TAG_SELF_OUT = 'selfOut';
33: public const PHPDOC_TAG_VAR = 'varTag';
34: public const INSTANTIATION = 'new';
35: public const TYPE_ALIAS = 'typeAlias';
36: public const PHPDOC_TAG_METHOD = 'methodTag';
37: public const PHPDOC_TAG_MIXIN = 'mixin';
38: public const PHPDOC_TAG_PROPERTY = 'propertyTag';
39: public const PHPDOC_TAG_REQUIRE_EXTENDS = 'requireExtends';
40: public const PHPDOC_TAG_REQUIRE_IMPLEMENTS = 'requireImplements';
41: public const STATIC_METHOD_CALL = 'staticMethod';
42: public const PHPDOC_TAG_TEMPLATE_BOUND = 'templateBound';
43: public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'templateDefault';
44:
45: /**
46: * @param self::* $value
47: * @param mixed[] $data
48: */
49: private function __construct(public readonly string $value, public readonly array $data)
50: {
51: }
52:
53: /**
54: * @param self::* $value
55: * @param mixed[] $data
56: */
57: public static function from(string $value, array $data = []): self
58: {
59: return new self($value, $data);
60: }
61:
62: public function getMethod(): ?ExtendedMethodReflection
63: {
64: return $this->data['method'] ?? null;
65: }
66:
67: public function getProperty(): ?ExtendedPropertyReflection
68: {
69: return $this->data['property'] ?? null;
70: }
71:
72: public function getFunction(): ?FunctionReflection
73: {
74: return $this->data['function'] ?? null;
75: }
76:
77: public function getPhpDocTagName(): ?string
78: {
79: return $this->data['phpDocTagName'] ?? null;
80: }
81:
82: public function getAssertedExprString(): ?string
83: {
84: return $this->data['assertedExprString'] ?? null;
85: }
86:
87: public function getClassConstant(): ?ClassConstantReflection
88: {
89: return $this->data['classConstant'] ?? null;
90: }
91:
92: public function getCurrentClassName(): ?string
93: {
94: return $this->data['currentClassName'] ?? null;
95: }
96:
97: public function getParameterName(): ?string
98: {
99: return $this->data['parameterName'] ?? null;
100: }
101:
102: public function getTypeAliasName(): ?string
103: {
104: return $this->data['typeAliasName'] ?? null;
105: }
106:
107: public function getMethodTagName(): ?string
108: {
109: return $this->data['methodTagName'] ?? null;
110: }
111:
112: public function getPropertyTagName(): ?string
113: {
114: return $this->data['propertyTagName'] ?? null;
115: }
116:
117: public function getTemplateTagName(): ?string
118: {
119: return $this->data['templateTagName'] ?? null;
120: }
121:
122: public function isInAnomyousFunction(): bool
123: {
124: return $this->data['isInAnonymousFunction'] ?? false;
125: }
126:
127: public function createMessage(string $part): string
128: {
129: switch ($this->value) {
130: case self::TRAIT_USE:
131: if ($this->getCurrentClassName() !== null) {
132: return sprintf('Usage of %s in class %s.', $part, $this->getCurrentClassName());
133: }
134: return sprintf('Usage of %s.', $part);
135: case self::STATIC_PROPERTY_ACCESS:
136: $property = $this->getProperty();
137: if ($property !== null) {
138: return sprintf('Access to static property $%s on %s.', $property->getName(), $part);
139: }
140:
141: return sprintf('Access to static property on %s.', $part);
142: case self::PHPDOC_TAG_ASSERT:
143: $phpDocTagName = $this->getPhpDocTagName();
144: $assertExprString = $this->getAssertedExprString();
145: if ($phpDocTagName !== null && $assertExprString !== null) {
146: return sprintf('PHPDoc tag %s for %s references %s.', $phpDocTagName, $assertExprString, $part);
147: }
148:
149: return sprintf('Assert tag references %s.', $part);
150: case self::ATTRIBUTE:
151: return sprintf('Attribute references %s.', $part);
152: case self::EXCEPTION_CATCH:
153: return sprintf('Catching %s.', $part);
154: case self::CLASS_CONSTANT_ACCESS:
155: if ($this->getClassConstant() !== null) {
156: return sprintf('Access to constant %s on %s.', $this->getClassConstant()->getName(), $part);
157: }
158: return sprintf('Access to constant on %s.', $part);
159: case self::CLASS_IMPLEMENTS:
160: if ($this->getCurrentClassName() !== null) {
161: return sprintf('Class %s implements %s.', $this->getCurrentClassName(), $part);
162: }
163:
164: return sprintf('Anonymous class implements %s.', $part);
165: case self::ENUM_IMPLEMENTS:
166: if ($this->getCurrentClassName() !== null) {
167: return sprintf('Enum %s implements %s.', $this->getCurrentClassName(), $part);
168: }
169:
170: return sprintf('Enum implements %s.', $part);
171: case self::INTERFACE_EXTENDS:
172: if ($this->getCurrentClassName() !== null) {
173: return sprintf('Interface %s extends %s.', $this->getCurrentClassName(), $part);
174: }
175:
176: return sprintf('Interface extends %s.', $part);
177: case self::CLASS_EXTENDS:
178: if ($this->getCurrentClassName() !== null) {
179: return sprintf('Class %s extends %s.', $this->getCurrentClassName(), $part);
180: }
181:
182: return sprintf('Anonymous class extends %s.', $part);
183: case self::INSTANCEOF:
184: return sprintf('Instanceof references %s.', $part);
185: case self::PROPERTY_TYPE:
186: $property = $this->getProperty();
187: if ($property !== null) {
188: return sprintf('Property $%s references %s in its type.', $property->getName(), $part);
189: }
190: return sprintf('Property references %s in its type.', $part);
191: case self::PARAMETER_TYPE:
192: $parameterName = $this->getParameterName();
193: if ($parameterName !== null) {
194: if ($this->isInAnomyousFunction()) {
195: return sprintf('Parameter $%s of anonymous function has typehint with %s.', $parameterName, $part);
196: }
197: if ($this->getMethod() !== null) {
198: if ($this->getCurrentClassName() !== null) {
199: return sprintf('Parameter $%s of method %s::%s() has typehint with %s.', $parameterName, $this->getCurrentClassName(), $this->getMethod()->getName(), $part);
200: }
201:
202: return sprintf('Parameter $%s of method %s() in anonymous class has typehint with %s.', $parameterName, $this->getMethod()->getName(), $part);
203: }
204:
205: if ($this->getFunction() !== null) {
206: return sprintf('Parameter $%s of function %s() has typehint with %s.', $parameterName, $this->getFunction()->getName(), $part);
207: }
208:
209: return sprintf('Parameter $%s has typehint with %s.', $parameterName, $part);
210: }
211:
212: return sprintf('Parameter has typehint with %s.', $part);
213: case self::RETURN_TYPE:
214: if ($this->isInAnomyousFunction()) {
215: return sprintf('Return type of anonymous function has typehint with %s.', $part);
216: }
217: if ($this->getMethod() !== null) {
218: if ($this->getCurrentClassName() !== null) {
219: return sprintf('Return type of method %s::%s() has typehint with %s.', $this->getCurrentClassName(), $this->getMethod()->getName(), $part);
220: }
221:
222: return sprintf('Return type of method %s() in anonymous class has typehint with %s.', $this->getMethod()->getName(), $part);
223: }
224:
225: if ($this->getFunction() !== null) {
226: return sprintf('Return type of function %s() has typehint with %s.', $this->getFunction()->getName(), $part);
227: }
228:
229: return sprintf('Return type has typehint with %s.', $part);
230: case self::PHPDOC_TAG_SELF_OUT:
231: return sprintf('PHPDoc tag @phpstan-self-out references %s.', $part);
232: case self::PHPDOC_TAG_VAR:
233: return sprintf('PHPDoc tag @var references %s.', $part);
234: case self::INSTANTIATION:
235: return sprintf('Instantiation of %s.', $part);
236: case self::TYPE_ALIAS:
237: if ($this->getTypeAliasName() !== null) {
238: return sprintf('Type alias %s references %s.', $this->getTypeAliasName(), $part);
239: }
240:
241: return sprintf('Type alias references %s.', $part);
242: case self::PHPDOC_TAG_METHOD:
243: if ($this->getMethodTagName() !== null) {
244: return sprintf('PHPDoc tag @method for %s() references %s.', $this->getMethodTagName(), $part);
245: }
246: return sprintf('PHPDoc tag @method references %s.', $part);
247: case self::PHPDOC_TAG_MIXIN:
248: return sprintf('PHPDoc tag @mixin references %s.', $part);
249: case self::PHPDOC_TAG_PROPERTY:
250: if ($this->getPropertyTagName() !== null) {
251: return sprintf('PHPDoc tag @property for $%s references %s.', $this->getPropertyTagName(), $part);
252: }
253: return sprintf('PHPDoc tag @property references %s.', $part);
254: case self::PHPDOC_TAG_REQUIRE_EXTENDS:
255: return sprintf('PHPDoc tag @phpstan-require-extends references %s.', $part);
256: case self::PHPDOC_TAG_REQUIRE_IMPLEMENTS:
257: return sprintf('PHPDoc tag @phpstan-require-implements references %s.', $part);
258: case self::STATIC_METHOD_CALL:
259: $method = $this->getMethod();
260: if ($method !== null) {
261: return sprintf('Call to static method %s() on %s.', $method->getName(), $part);
262: }
263:
264: return sprintf('Call to static method on %s.', $part);
265: case self::PHPDOC_TAG_TEMPLATE_BOUND:
266: if ($this->getTemplateTagName() !== null) {
267: return sprintf('PHPDoc tag @template %s bound references %s.', $this->getTemplateTagName(), $part);
268: }
269:
270: return sprintf('PHPDoc tag @template bound references %s.', $part);
271: case self::PHPDOC_TAG_TEMPLATE_DEFAULT:
272: if ($this->getTemplateTagName() !== null) {
273: return sprintf('PHPDoc tag @template %s default references %s.', $this->getTemplateTagName(), $part);
274: }
275:
276: return sprintf('PHPDoc tag @template default references %s.', $part);
277: }
278: }
279:
280: public function createIdentifier(string $secondPart): string
281: {
282: if ($this->value === self::CLASS_IMPLEMENTS) {
283: return sprintf('class.implements%s', ucfirst($secondPart));
284: }
285: if ($this->value === self::ENUM_IMPLEMENTS) {
286: return sprintf('enum.implements%s', ucfirst($secondPart));
287: }
288: if ($this->value === self::INTERFACE_EXTENDS) {
289: return sprintf('interface.extends%s', ucfirst($secondPart));
290: }
291: if ($this->value === self::CLASS_EXTENDS) {
292: return sprintf('class.extends%s', ucfirst($secondPart));
293: }
294: if ($this->value === self::PHPDOC_TAG_TEMPLATE_BOUND) {
295: return sprintf('generics.%sBound', $secondPart);
296: }
297: if ($this->value === self::PHPDOC_TAG_TEMPLATE_DEFAULT) {
298: return sprintf('generics.%sDefault', $secondPart);
299: }
300:
301: return sprintf('%s.%s', $this->value, $secondPart);
302: }
303:
304: }
305: