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