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