1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\PhpDoc; |
4: | |
5: | use PHPStan\Analyser\NameScope; |
6: | use PHPStan\PhpDoc\Tag\AssertTag; |
7: | use PHPStan\PhpDoc\Tag\DeprecatedTag; |
8: | use PHPStan\PhpDoc\Tag\ExtendsTag; |
9: | use PHPStan\PhpDoc\Tag\ImplementsTag; |
10: | use PHPStan\PhpDoc\Tag\MethodTag; |
11: | use PHPStan\PhpDoc\Tag\MixinTag; |
12: | use PHPStan\PhpDoc\Tag\ParamOutTag; |
13: | use PHPStan\PhpDoc\Tag\ParamTag; |
14: | use PHPStan\PhpDoc\Tag\PropertyTag; |
15: | use PHPStan\PhpDoc\Tag\ReturnTag; |
16: | use PHPStan\PhpDoc\Tag\SelfOutTypeTag; |
17: | use PHPStan\PhpDoc\Tag\TemplateTag; |
18: | use PHPStan\PhpDoc\Tag\ThrowsTag; |
19: | use PHPStan\PhpDoc\Tag\TypeAliasImportTag; |
20: | use PHPStan\PhpDoc\Tag\TypeAliasTag; |
21: | use PHPStan\PhpDoc\Tag\TypedTag; |
22: | use PHPStan\PhpDoc\Tag\UsesTag; |
23: | use PHPStan\PhpDoc\Tag\VarTag; |
24: | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; |
25: | use PHPStan\Type\ConditionalTypeForParameter; |
26: | use PHPStan\Type\Generic\TemplateTypeHelper; |
27: | use PHPStan\Type\Generic\TemplateTypeMap; |
28: | use PHPStan\Type\Type; |
29: | use PHPStan\Type\TypeTraverser; |
30: | use function array_key_exists; |
31: | use function array_map; |
32: | use function count; |
33: | use function is_bool; |
34: | use function substr; |
35: | |
36: | |
37: | class ResolvedPhpDocBlock |
38: | { |
39: | |
40: | public const EMPTY_DOC_STRING = '/** */'; |
41: | |
42: | private PhpDocNode $phpDocNode; |
43: | |
44: | |
45: | private array $phpDocNodes; |
46: | |
47: | private string $phpDocString; |
48: | |
49: | private ?string $filename; |
50: | |
51: | private ?NameScope $nameScope = null; |
52: | |
53: | private TemplateTypeMap $templateTypeMap; |
54: | |
55: | |
56: | private array $templateTags; |
57: | |
58: | private PhpDocNodeResolver $phpDocNodeResolver; |
59: | |
60: | |
61: | private array|false $varTags = false; |
62: | |
63: | |
64: | private array|false $methodTags = false; |
65: | |
66: | |
67: | private array|false $propertyTags = false; |
68: | |
69: | |
70: | private array|false $extendsTags = false; |
71: | |
72: | |
73: | private array|false $implementsTags = false; |
74: | |
75: | |
76: | private array|false $usesTags = false; |
77: | |
78: | |
79: | private array|false $paramTags = false; |
80: | |
81: | |
82: | private array|false $paramOutTags = false; |
83: | |
84: | private ReturnTag|false|null $returnTag = false; |
85: | |
86: | private ThrowsTag|false|null $throwsTag = false; |
87: | |
88: | |
89: | private array|false $mixinTags = false; |
90: | |
91: | |
92: | private array|false $typeAliasTags = false; |
93: | |
94: | |
95: | private array|false $typeAliasImportTags = false; |
96: | |
97: | |
98: | private array|false $assertTags = false; |
99: | |
100: | private SelfOutTypeTag|false|null $selfOutTypeTag = false; |
101: | |
102: | private DeprecatedTag|false|null $deprecatedTag = false; |
103: | |
104: | private ?bool $isDeprecated = null; |
105: | |
106: | private ?bool $isNotDeprecated = null; |
107: | |
108: | private ?bool $isInternal = null; |
109: | |
110: | private ?bool $isFinal = null; |
111: | |
112: | |
113: | private bool|string|null $isPure = 'notLoaded'; |
114: | |
115: | private ?bool $isReadOnly = null; |
116: | |
117: | private ?bool $isImmutable = null; |
118: | |
119: | private ?bool $isAllowedPrivateMutation = null; |
120: | |
121: | private ?bool $hasConsistentConstructor = null; |
122: | |
123: | private ?bool $acceptsNamedArguments = null; |
124: | |
125: | private function __construct() |
126: | { |
127: | } |
128: | |
129: | |
130: | |
131: | |
132: | public static function create( |
133: | PhpDocNode $phpDocNode, |
134: | string $phpDocString, |
135: | ?string $filename, |
136: | NameScope $nameScope, |
137: | TemplateTypeMap $templateTypeMap, |
138: | array $templateTags, |
139: | PhpDocNodeResolver $phpDocNodeResolver, |
140: | ): self |
141: | { |
142: | |
143: | $self = new self(); |
144: | $self->phpDocNode = $phpDocNode; |
145: | $self->phpDocNodes = [$phpDocNode]; |
146: | $self->phpDocString = $phpDocString; |
147: | $self->filename = $filename; |
148: | $self->nameScope = $nameScope; |
149: | $self->templateTypeMap = $templateTypeMap; |
150: | $self->templateTags = $templateTags; |
151: | $self->phpDocNodeResolver = $phpDocNodeResolver; |
152: | |
153: | return $self; |
154: | } |
155: | |
156: | public static function createEmpty(): self |
157: | { |
158: | |
159: | $self = new self(); |
160: | $self->phpDocString = self::EMPTY_DOC_STRING; |
161: | $self->phpDocNodes = []; |
162: | $self->filename = null; |
163: | $self->templateTypeMap = TemplateTypeMap::createEmpty(); |
164: | $self->templateTags = []; |
165: | $self->varTags = []; |
166: | $self->methodTags = []; |
167: | $self->propertyTags = []; |
168: | $self->extendsTags = []; |
169: | $self->implementsTags = []; |
170: | $self->usesTags = []; |
171: | $self->paramTags = []; |
172: | $self->paramOutTags = []; |
173: | $self->returnTag = null; |
174: | $self->throwsTag = null; |
175: | $self->mixinTags = []; |
176: | $self->typeAliasTags = []; |
177: | $self->typeAliasImportTags = []; |
178: | $self->assertTags = []; |
179: | $self->selfOutTypeTag = null; |
180: | $self->deprecatedTag = null; |
181: | $self->isDeprecated = false; |
182: | $self->isNotDeprecated = false; |
183: | $self->isInternal = false; |
184: | $self->isFinal = false; |
185: | $self->isPure = null; |
186: | $self->isReadOnly = false; |
187: | $self->isImmutable = false; |
188: | $self->isAllowedPrivateMutation = false; |
189: | $self->hasConsistentConstructor = false; |
190: | $self->acceptsNamedArguments = true; |
191: | |
192: | return $self; |
193: | } |
194: | |
195: | |
196: | |
197: | |
198: | |
199: | public function merge(array $parents, array $parentPhpDocBlocks): self |
200: | { |
201: | |
202: | $result = new self(); |
203: | |
204: | |
205: | $phpDocNodes = $this->phpDocNodes; |
206: | $acceptsNamedArguments = $this->acceptsNamedArguments(); |
207: | foreach ($parents as $parent) { |
208: | foreach ($parent->phpDocNodes as $phpDocNode) { |
209: | $phpDocNodes[] = $phpDocNode; |
210: | $acceptsNamedArguments = $acceptsNamedArguments && $parent->acceptsNamedArguments(); |
211: | } |
212: | } |
213: | $result->phpDocNodes = $phpDocNodes; |
214: | $result->phpDocString = $this->phpDocString; |
215: | $result->filename = $this->filename; |
216: | |
217: | $result->templateTypeMap = $this->templateTypeMap; |
218: | $result->templateTags = $this->templateTags; |
219: | |
220: | $result->varTags = self::mergeVarTags($this->getVarTags(), $parents, $parentPhpDocBlocks); |
221: | $result->methodTags = $this->getMethodTags(); |
222: | $result->propertyTags = $this->getPropertyTags(); |
223: | $result->extendsTags = $this->getExtendsTags(); |
224: | $result->implementsTags = $this->getImplementsTags(); |
225: | $result->usesTags = $this->getUsesTags(); |
226: | $result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks); |
227: | $result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parents, $parentPhpDocBlocks); |
228: | $result->returnTag = self::mergeReturnTags($this->getReturnTag(), $parents, $parentPhpDocBlocks); |
229: | $result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents); |
230: | $result->mixinTags = $this->getMixinTags(); |
231: | $result->typeAliasTags = $this->getTypeAliasTags(); |
232: | $result->typeAliasImportTags = $this->getTypeAliasImportTags(); |
233: | $result->assertTags = self::mergeAssertTags($this->getAssertTags(), $parents, $parentPhpDocBlocks); |
234: | $result->selfOutTypeTag = self::mergeSelfOutTypeTags($this->getSelfOutTag(), $parents); |
235: | $result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $this->isNotDeprecated(), $parents); |
236: | $result->isDeprecated = $result->deprecatedTag !== null; |
237: | $result->isNotDeprecated = $this->isNotDeprecated(); |
238: | $result->isInternal = $this->isInternal(); |
239: | $result->isFinal = $this->isFinal(); |
240: | $result->isPure = $this->isPure(); |
241: | $result->isReadOnly = $this->isReadOnly(); |
242: | $result->isImmutable = $this->isImmutable(); |
243: | $result->isAllowedPrivateMutation = $this->isAllowedPrivateMutation(); |
244: | $result->hasConsistentConstructor = $this->hasConsistentConstructor(); |
245: | $result->acceptsNamedArguments = $acceptsNamedArguments; |
246: | |
247: | return $result; |
248: | } |
249: | |
250: | |
251: | |
252: | |
253: | public function changeParameterNamesByMapping(array $parameterNameMapping): self |
254: | { |
255: | if (count($this->phpDocNodes) === 0) { |
256: | return $this; |
257: | } |
258: | |
259: | $paramTags = $this->getParamTags(); |
260: | |
261: | $newParamTags = []; |
262: | foreach ($paramTags as $key => $paramTag) { |
263: | if (!array_key_exists($key, $parameterNameMapping)) { |
264: | continue; |
265: | } |
266: | $newParamTags[$parameterNameMapping[$key]] = $paramTag; |
267: | } |
268: | |
269: | $paramOutTags = $this->getParamOutTags(); |
270: | |
271: | $newParamOutTags = []; |
272: | foreach ($paramOutTags as $key => $paramOutTag) { |
273: | if (!array_key_exists($key, $parameterNameMapping)) { |
274: | continue; |
275: | } |
276: | $newParamOutTags[$parameterNameMapping[$key]] = $paramOutTag; |
277: | } |
278: | |
279: | $returnTag = $this->getReturnTag(); |
280: | if ($returnTag !== null) { |
281: | $transformedType = TypeTraverser::map($returnTag->getType(), static function (Type $type, callable $traverse) use ($parameterNameMapping): Type { |
282: | if ($type instanceof ConditionalTypeForParameter) { |
283: | $parameterName = substr($type->getParameterName(), 1); |
284: | if (array_key_exists($parameterName, $parameterNameMapping)) { |
285: | $type = $type->changeParameterName('$' . $parameterNameMapping[$parameterName]); |
286: | } |
287: | } |
288: | |
289: | return $traverse($type); |
290: | }); |
291: | $returnTag = $returnTag->withType($transformedType); |
292: | } |
293: | |
294: | $assertTags = $this->getAssertTags(); |
295: | if (count($assertTags) > 0) { |
296: | $assertTags = array_map(static function (AssertTag $tag) use ($parameterNameMapping): AssertTag { |
297: | $parameterName = substr($tag->getParameter()->getParameterName(), 1); |
298: | if (array_key_exists($parameterName, $parameterNameMapping)) { |
299: | $tag = $tag->withParameter($tag->getParameter()->changeParameterName('$' . $parameterNameMapping[$parameterName])); |
300: | } |
301: | return $tag; |
302: | }, $assertTags); |
303: | } |
304: | |
305: | $self = new self(); |
306: | $self->phpDocNode = $this->phpDocNode; |
307: | $self->phpDocNodes = $this->phpDocNodes; |
308: | $self->phpDocString = $this->phpDocString; |
309: | $self->filename = $this->filename; |
310: | $self->nameScope = $this->nameScope; |
311: | $self->templateTypeMap = $this->templateTypeMap; |
312: | $self->templateTags = $this->templateTags; |
313: | $self->phpDocNodeResolver = $this->phpDocNodeResolver; |
314: | $self->varTags = $this->varTags; |
315: | $self->methodTags = $this->methodTags; |
316: | $self->propertyTags = $this->propertyTags; |
317: | $self->extendsTags = $this->extendsTags; |
318: | $self->implementsTags = $this->implementsTags; |
319: | $self->usesTags = $this->usesTags; |
320: | $self->paramTags = $newParamTags; |
321: | $self->paramOutTags = $newParamOutTags; |
322: | $self->returnTag = $returnTag; |
323: | $self->throwsTag = $this->throwsTag; |
324: | $self->mixinTags = $this->mixinTags; |
325: | $self->typeAliasTags = $this->typeAliasTags; |
326: | $self->typeAliasImportTags = $this->typeAliasImportTags; |
327: | $self->assertTags = $assertTags; |
328: | $self->selfOutTypeTag = $this->selfOutTypeTag; |
329: | $self->deprecatedTag = $this->deprecatedTag; |
330: | $self->isDeprecated = $this->isDeprecated; |
331: | $self->isNotDeprecated = $this->isNotDeprecated; |
332: | $self->isInternal = $this->isInternal; |
333: | $self->isFinal = $this->isFinal; |
334: | $self->isPure = $this->isPure; |
335: | |
336: | return $self; |
337: | } |
338: | |
339: | public function hasPhpDocString(): bool |
340: | { |
341: | return $this->phpDocString !== self::EMPTY_DOC_STRING; |
342: | } |
343: | |
344: | public function getPhpDocString(): string |
345: | { |
346: | return $this->phpDocString; |
347: | } |
348: | |
349: | |
350: | |
351: | |
352: | public function getPhpDocNodes(): array |
353: | { |
354: | return $this->phpDocNodes; |
355: | } |
356: | |
357: | public function getFilename(): ?string |
358: | { |
359: | return $this->filename; |
360: | } |
361: | |
362: | private function getNameScope(): NameScope |
363: | { |
364: | return $this->nameScope; |
365: | } |
366: | |
367: | public function getNullableNameScope(): ?NameScope |
368: | { |
369: | return $this->nameScope; |
370: | } |
371: | |
372: | |
373: | |
374: | |
375: | public function getVarTags(): array |
376: | { |
377: | if ($this->varTags === false) { |
378: | $this->varTags = $this->phpDocNodeResolver->resolveVarTags( |
379: | $this->phpDocNode, |
380: | $this->getNameScope(), |
381: | ); |
382: | } |
383: | return $this->varTags; |
384: | } |
385: | |
386: | |
387: | |
388: | |
389: | public function getMethodTags(): array |
390: | { |
391: | if ($this->methodTags === false) { |
392: | $this->methodTags = $this->phpDocNodeResolver->resolveMethodTags( |
393: | $this->phpDocNode, |
394: | $this->getNameScope(), |
395: | ); |
396: | } |
397: | return $this->methodTags; |
398: | } |
399: | |
400: | |
401: | |
402: | |
403: | public function getPropertyTags(): array |
404: | { |
405: | if ($this->propertyTags === false) { |
406: | $this->propertyTags = $this->phpDocNodeResolver->resolvePropertyTags( |
407: | $this->phpDocNode, |
408: | $this->getNameScope(), |
409: | ); |
410: | } |
411: | return $this->propertyTags; |
412: | } |
413: | |
414: | |
415: | |
416: | |
417: | public function getTemplateTags(): array |
418: | { |
419: | return $this->templateTags; |
420: | } |
421: | |
422: | |
423: | |
424: | |
425: | public function getExtendsTags(): array |
426: | { |
427: | if ($this->extendsTags === false) { |
428: | $this->extendsTags = $this->phpDocNodeResolver->resolveExtendsTags( |
429: | $this->phpDocNode, |
430: | $this->getNameScope(), |
431: | ); |
432: | } |
433: | return $this->extendsTags; |
434: | } |
435: | |
436: | |
437: | |
438: | |
439: | public function getImplementsTags(): array |
440: | { |
441: | if ($this->implementsTags === false) { |
442: | $this->implementsTags = $this->phpDocNodeResolver->resolveImplementsTags( |
443: | $this->phpDocNode, |
444: | $this->getNameScope(), |
445: | ); |
446: | } |
447: | return $this->implementsTags; |
448: | } |
449: | |
450: | |
451: | |
452: | |
453: | public function getUsesTags(): array |
454: | { |
455: | if ($this->usesTags === false) { |
456: | $this->usesTags = $this->phpDocNodeResolver->resolveUsesTags( |
457: | $this->phpDocNode, |
458: | $this->getNameScope(), |
459: | ); |
460: | } |
461: | return $this->usesTags; |
462: | } |
463: | |
464: | |
465: | |
466: | |
467: | public function getParamTags(): array |
468: | { |
469: | if ($this->paramTags === false) { |
470: | $this->paramTags = $this->phpDocNodeResolver->resolveParamTags( |
471: | $this->phpDocNode, |
472: | $this->getNameScope(), |
473: | ); |
474: | } |
475: | return $this->paramTags; |
476: | } |
477: | |
478: | |
479: | |
480: | |
481: | public function getParamOutTags(): array |
482: | { |
483: | if ($this->paramOutTags === false) { |
484: | $this->paramOutTags = $this->phpDocNodeResolver->resolveParamOutTags( |
485: | $this->phpDocNode, |
486: | $this->getNameScope(), |
487: | ); |
488: | } |
489: | return $this->paramOutTags; |
490: | } |
491: | |
492: | public function getReturnTag(): ?ReturnTag |
493: | { |
494: | if (is_bool($this->returnTag)) { |
495: | $this->returnTag = $this->phpDocNodeResolver->resolveReturnTag( |
496: | $this->phpDocNode, |
497: | $this->getNameScope(), |
498: | ); |
499: | } |
500: | return $this->returnTag; |
501: | } |
502: | |
503: | public function getThrowsTag(): ?ThrowsTag |
504: | { |
505: | if (is_bool($this->throwsTag)) { |
506: | $this->throwsTag = $this->phpDocNodeResolver->resolveThrowsTags( |
507: | $this->phpDocNode, |
508: | $this->getNameScope(), |
509: | ); |
510: | } |
511: | return $this->throwsTag; |
512: | } |
513: | |
514: | |
515: | |
516: | |
517: | public function getMixinTags(): array |
518: | { |
519: | if ($this->mixinTags === false) { |
520: | $this->mixinTags = $this->phpDocNodeResolver->resolveMixinTags( |
521: | $this->phpDocNode, |
522: | $this->getNameScope(), |
523: | ); |
524: | } |
525: | |
526: | return $this->mixinTags; |
527: | } |
528: | |
529: | |
530: | |
531: | |
532: | public function getTypeAliasTags(): array |
533: | { |
534: | if ($this->typeAliasTags === false) { |
535: | $this->typeAliasTags = $this->phpDocNodeResolver->resolveTypeAliasTags( |
536: | $this->phpDocNode, |
537: | $this->getNameScope(), |
538: | ); |
539: | } |
540: | |
541: | return $this->typeAliasTags; |
542: | } |
543: | |
544: | |
545: | |
546: | |
547: | public function getTypeAliasImportTags(): array |
548: | { |
549: | if ($this->typeAliasImportTags === false) { |
550: | $this->typeAliasImportTags = $this->phpDocNodeResolver->resolveTypeAliasImportTags( |
551: | $this->phpDocNode, |
552: | $this->getNameScope(), |
553: | ); |
554: | } |
555: | |
556: | return $this->typeAliasImportTags; |
557: | } |
558: | |
559: | |
560: | |
561: | |
562: | public function getAssertTags(): array |
563: | { |
564: | if ($this->assertTags === false) { |
565: | $this->assertTags = $this->phpDocNodeResolver->resolveAssertTags( |
566: | $this->phpDocNode, |
567: | $this->getNameScope(), |
568: | ); |
569: | } |
570: | |
571: | return $this->assertTags; |
572: | } |
573: | |
574: | public function getSelfOutTag(): ?SelfOutTypeTag |
575: | { |
576: | if ($this->selfOutTypeTag === false) { |
577: | $this->selfOutTypeTag = $this->phpDocNodeResolver->resolveSelfOutTypeTag( |
578: | $this->phpDocNode, |
579: | $this->getNameScope(), |
580: | ); |
581: | } |
582: | |
583: | return $this->selfOutTypeTag; |
584: | } |
585: | |
586: | public function getDeprecatedTag(): ?DeprecatedTag |
587: | { |
588: | if (is_bool($this->deprecatedTag)) { |
589: | $this->deprecatedTag = $this->phpDocNodeResolver->resolveDeprecatedTag( |
590: | $this->phpDocNode, |
591: | $this->getNameScope(), |
592: | ); |
593: | } |
594: | return $this->deprecatedTag; |
595: | } |
596: | |
597: | public function isDeprecated(): bool |
598: | { |
599: | if ($this->isDeprecated === null) { |
600: | $this->isDeprecated = $this->phpDocNodeResolver->resolveIsDeprecated( |
601: | $this->phpDocNode, |
602: | ); |
603: | } |
604: | return $this->isDeprecated; |
605: | } |
606: | |
607: | |
608: | |
609: | |
610: | public function isNotDeprecated(): bool |
611: | { |
612: | if ($this->isNotDeprecated === null) { |
613: | $this->isNotDeprecated = $this->phpDocNodeResolver->resolveIsNotDeprecated( |
614: | $this->phpDocNode, |
615: | ); |
616: | } |
617: | return $this->isNotDeprecated; |
618: | } |
619: | |
620: | public function isInternal(): bool |
621: | { |
622: | if ($this->isInternal === null) { |
623: | $this->isInternal = $this->phpDocNodeResolver->resolveIsInternal( |
624: | $this->phpDocNode, |
625: | ); |
626: | } |
627: | return $this->isInternal; |
628: | } |
629: | |
630: | public function isFinal(): bool |
631: | { |
632: | if ($this->isFinal === null) { |
633: | $this->isFinal = $this->phpDocNodeResolver->resolveIsFinal( |
634: | $this->phpDocNode, |
635: | ); |
636: | } |
637: | return $this->isFinal; |
638: | } |
639: | |
640: | public function hasConsistentConstructor(): bool |
641: | { |
642: | if ($this->hasConsistentConstructor === null) { |
643: | $this->hasConsistentConstructor = $this->phpDocNodeResolver->resolveHasConsistentConstructor( |
644: | $this->phpDocNode, |
645: | ); |
646: | } |
647: | return $this->hasConsistentConstructor; |
648: | } |
649: | |
650: | public function acceptsNamedArguments(): bool |
651: | { |
652: | if ($this->acceptsNamedArguments === null) { |
653: | $this->acceptsNamedArguments = $this->phpDocNodeResolver->resolveAcceptsNamedArguments( |
654: | $this->phpDocNode, |
655: | ); |
656: | } |
657: | return $this->acceptsNamedArguments; |
658: | } |
659: | |
660: | public function getTemplateTypeMap(): TemplateTypeMap |
661: | { |
662: | return $this->templateTypeMap; |
663: | } |
664: | |
665: | public function isPure(): ?bool |
666: | { |
667: | if ($this->isPure === 'notLoaded') { |
668: | $pure = $this->phpDocNodeResolver->resolveIsPure( |
669: | $this->phpDocNode, |
670: | ); |
671: | if ($pure) { |
672: | $this->isPure = true; |
673: | return $this->isPure; |
674: | } |
675: | |
676: | $impure = $this->phpDocNodeResolver->resolveIsImpure( |
677: | $this->phpDocNode, |
678: | ); |
679: | if ($impure) { |
680: | $this->isPure = false; |
681: | return $this->isPure; |
682: | } |
683: | |
684: | $this->isPure = null; |
685: | } |
686: | |
687: | return $this->isPure; |
688: | } |
689: | |
690: | public function isReadOnly(): bool |
691: | { |
692: | if ($this->isReadOnly === null) { |
693: | $this->isReadOnly = $this->phpDocNodeResolver->resolveIsReadOnly( |
694: | $this->phpDocNode, |
695: | ); |
696: | } |
697: | return $this->isReadOnly; |
698: | } |
699: | |
700: | public function isImmutable(): bool |
701: | { |
702: | if ($this->isImmutable === null) { |
703: | $this->isImmutable = $this->phpDocNodeResolver->resolveIsImmutable( |
704: | $this->phpDocNode, |
705: | ); |
706: | } |
707: | return $this->isImmutable; |
708: | } |
709: | |
710: | public function isAllowedPrivateMutation(): bool |
711: | { |
712: | if ($this->isAllowedPrivateMutation === null) { |
713: | $this->isAllowedPrivateMutation = $this->phpDocNodeResolver->resolveAllowPrivateMutation( |
714: | $this->phpDocNode, |
715: | ); |
716: | } |
717: | |
718: | return $this->isAllowedPrivateMutation; |
719: | } |
720: | |
721: | |
722: | |
723: | |
724: | |
725: | |
726: | |
727: | private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array |
728: | { |
729: | |
730: | if (count($varTags) > 0) { |
731: | return $varTags; |
732: | } |
733: | |
734: | foreach ($parents as $i => $parent) { |
735: | $result = self::mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]); |
736: | if ($result === null) { |
737: | continue; |
738: | } |
739: | |
740: | return $result; |
741: | } |
742: | |
743: | return []; |
744: | } |
745: | |
746: | |
747: | |
748: | |
749: | |
750: | private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array |
751: | { |
752: | foreach ($parent->getVarTags() as $key => $parentVarTag) { |
753: | return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock)]; |
754: | } |
755: | |
756: | return null; |
757: | } |
758: | |
759: | |
760: | |
761: | |
762: | |
763: | |
764: | |
765: | private static function mergeParamTags(array $paramTags, array $parents, array $parentPhpDocBlocks): array |
766: | { |
767: | foreach ($parents as $i => $parent) { |
768: | $paramTags = self::mergeOneParentParamTags($paramTags, $parent, $parentPhpDocBlocks[$i]); |
769: | } |
770: | |
771: | return $paramTags; |
772: | } |
773: | |
774: | |
775: | |
776: | |
777: | |
778: | |
779: | private static function mergeOneParentParamTags(array $paramTags, self $parent, PhpDocBlock $phpDocBlock): array |
780: | { |
781: | $parentParamTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamTags()); |
782: | |
783: | foreach ($parentParamTags as $name => $parentParamTag) { |
784: | if (array_key_exists($name, $paramTags)) { |
785: | continue; |
786: | } |
787: | |
788: | $paramTags[$name] = self::resolveTemplateTypeInTag($parentParamTag, $phpDocBlock); |
789: | } |
790: | |
791: | return $paramTags; |
792: | } |
793: | |
794: | |
795: | |
796: | |
797: | |
798: | |
799: | private static function mergeReturnTags(?ReturnTag $returnTag, array $parents, array $parentPhpDocBlocks): ?ReturnTag |
800: | { |
801: | if ($returnTag !== null) { |
802: | return $returnTag; |
803: | } |
804: | |
805: | foreach ($parents as $i => $parent) { |
806: | $result = self::mergeOneParentReturnTag($returnTag, $parent, $parentPhpDocBlocks[$i]); |
807: | if ($result === null) { |
808: | continue; |
809: | } |
810: | |
811: | return $result; |
812: | } |
813: | |
814: | return null; |
815: | } |
816: | |
817: | private static function mergeOneParentReturnTag(?ReturnTag $returnTag, self $parent, PhpDocBlock $phpDocBlock): ?ReturnTag |
818: | { |
819: | $parentReturnTag = $parent->getReturnTag(); |
820: | if ($parentReturnTag === null) { |
821: | return $returnTag; |
822: | } |
823: | |
824: | $parentType = $parentReturnTag->getType(); |
825: | |
826: | |
827: | |
828: | if ($returnTag !== null && $parentType->isSuperTypeOf($returnTag->getType())->yes()) { |
829: | return null; |
830: | } |
831: | |
832: | return self::resolveTemplateTypeInTag( |
833: | $parentReturnTag->withType( |
834: | $phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentReturnTag->getType()), |
835: | )->toImplicit(), |
836: | $phpDocBlock, |
837: | ); |
838: | } |
839: | |
840: | |
841: | |
842: | |
843: | |
844: | |
845: | |
846: | private static function mergeAssertTags(array $assertTags, array $parents, array $parentPhpDocBlocks): array |
847: | { |
848: | if (count($assertTags) > 0) { |
849: | return $assertTags; |
850: | } |
851: | foreach ($parents as $i => $parent) { |
852: | $result = $parent->getAssertTags(); |
853: | if (count($result) === 0) { |
854: | continue; |
855: | } |
856: | |
857: | $phpDocBlock = $parentPhpDocBlocks[$i]; |
858: | |
859: | return array_map( |
860: | static fn (AssertTag $assertTag) => $assertTag->withParameter( |
861: | $phpDocBlock->transformAssertTagParameterWithParameterNameMapping($assertTag->getParameter()), |
862: | ), |
863: | $result, |
864: | ); |
865: | } |
866: | |
867: | return $assertTags; |
868: | } |
869: | |
870: | |
871: | |
872: | |
873: | private static function mergeSelfOutTypeTags(?SelfOutTypeTag $selfOutTypeTag, array $parents): ?SelfOutTypeTag |
874: | { |
875: | if ($selfOutTypeTag !== null) { |
876: | return $selfOutTypeTag; |
877: | } |
878: | foreach ($parents as $parent) { |
879: | $result = $parent->getSelfOutTag(); |
880: | if ($result === null) { |
881: | continue; |
882: | } |
883: | return $result; |
884: | } |
885: | |
886: | return null; |
887: | } |
888: | |
889: | |
890: | |
891: | |
892: | private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, bool $hasNotDeprecatedTag, array $parents): ?DeprecatedTag |
893: | { |
894: | if ($deprecatedTag !== null) { |
895: | return $deprecatedTag; |
896: | } |
897: | |
898: | if ($hasNotDeprecatedTag) { |
899: | return null; |
900: | } |
901: | |
902: | foreach ($parents as $parent) { |
903: | $result = $parent->getDeprecatedTag(); |
904: | if ($result === null && !$parent->isNotDeprecated()) { |
905: | continue; |
906: | } |
907: | return $result; |
908: | } |
909: | |
910: | return null; |
911: | } |
912: | |
913: | |
914: | |
915: | |
916: | private static function mergeThrowsTags(?ThrowsTag $throwsTag, array $parents): ?ThrowsTag |
917: | { |
918: | if ($throwsTag !== null) { |
919: | return $throwsTag; |
920: | } |
921: | foreach ($parents as $parent) { |
922: | $result = $parent->getThrowsTag(); |
923: | if ($result === null) { |
924: | continue; |
925: | } |
926: | |
927: | return $result; |
928: | } |
929: | |
930: | return null; |
931: | } |
932: | |
933: | |
934: | |
935: | |
936: | |
937: | |
938: | |
939: | private static function mergeParamOutTags(array $paramOutTags, array $parents, array $parentPhpDocBlocks): array |
940: | { |
941: | foreach ($parents as $i => $parent) { |
942: | $paramOutTags = self::mergeOneParentParamOutTags($paramOutTags, $parent, $parentPhpDocBlocks[$i]); |
943: | } |
944: | |
945: | return $paramOutTags; |
946: | } |
947: | |
948: | |
949: | |
950: | |
951: | |
952: | |
953: | private static function mergeOneParentParamOutTags(array $paramOutTags, self $parent, PhpDocBlock $phpDocBlock): array |
954: | { |
955: | $parentParamOutTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamOutTags()); |
956: | |
957: | foreach ($parentParamOutTags as $name => $parentParamTag) { |
958: | if (array_key_exists($name, $paramOutTags)) { |
959: | continue; |
960: | } |
961: | |
962: | $paramOutTags[$name] = self::resolveTemplateTypeInTag($parentParamTag, $phpDocBlock); |
963: | } |
964: | |
965: | return $paramOutTags; |
966: | } |
967: | |
968: | |
969: | |
970: | |
971: | |
972: | |
973: | private static function resolveTemplateTypeInTag(TypedTag $tag, PhpDocBlock $phpDocBlock): TypedTag |
974: | { |
975: | $type = TemplateTypeHelper::resolveTemplateTypes( |
976: | $tag->getType(), |
977: | $phpDocBlock->getClassReflection()->getActiveTemplateTypeMap(), |
978: | ); |
979: | return $tag->withType($type); |
980: | } |
981: | |
982: | } |
983: | |