24
24
use PHPStan \PhpDocParser \Ast \Node as PhpDocRootNode ;
25
25
use PHPStan \PhpDocParser \Ast \PhpDoc \ParamTagValueNode ;
26
26
use PHPStan \PhpDocParser \Ast \PhpDoc \ReturnTagValueNode ;
27
+ use PHPStan \PhpDocParser \Ast \PhpDoc \ThrowsTagValueNode ;
27
28
use PHPStan \PhpDocParser \Ast \PhpDoc \VarTagValueNode ;
28
29
use PHPStan \PhpDocParser \Ast \Type \IdentifierTypeNode ;
29
30
use PHPStan \PhpDocParser \Ast \Type \IntersectionTypeNode ;
41
42
use function get_object_vars ;
42
43
use function implode ;
43
44
use function is_array ;
45
+ use function is_int ;
44
46
use function is_object ;
45
47
use function is_string ;
46
48
use function spl_object_id ;
@@ -92,7 +94,7 @@ public function processNode(
92
94
{
93
95
if ($ node instanceof FunctionLike) {
94
96
return array_merge (
95
- $ this ->checkParamAndReturnPhpDoc ($ node , $ scope ),
97
+ $ this ->checkParamAndReturnAndThrowsPhpDoc ($ node , $ scope ),
96
98
$ this ->checkParamAndReturnNativeType ($ node , $ scope ),
97
99
);
98
100
}
@@ -123,7 +125,7 @@ private function checkCatchNativeType(Catch_ $node, Scope $scope): array
123
125
/**
124
126
* @return list<RuleError>
125
127
*/
126
- private function checkParamAndReturnPhpDoc (
128
+ private function checkParamAndReturnAndThrowsPhpDoc (
127
129
FunctionLike $ node ,
128
130
Scope $ scope
129
131
): array
@@ -147,6 +149,7 @@ private function checkParamAndReturnPhpDoc(
147
149
$ errors ,
148
150
$ this ->processParamTags ($ node , $ phpdocNode ->getParamTagValues (), $ nameScope ),
149
151
$ this ->processReturnTags ($ node , $ phpdocNode ->getReturnTagValues (), $ nameScope ),
152
+ $ this ->processThrowsTags ($ node , $ phpdocNode ->getThrowsTagValues (), $ nameScope ),
150
153
);
151
154
}
152
155
@@ -315,12 +318,13 @@ public function processParamTags(
315
318
$ errors = [];
316
319
317
320
foreach ($ paramTagValues as $ paramTagValue ) {
318
- foreach ($ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ paramTagValue ->type ) as $ multiTypeNode ) {
321
+ $ line = $ this ->getPhpDocLine ($ sourceNode , $ paramTagValue );
322
+
323
+ foreach ($ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ paramTagValue ->type , $ line ) as $ multiTypeNode ) {
319
324
$ newErrors = $ this ->processMultiTypePhpDocNode (
320
325
$ multiTypeNode ,
321
326
$ nameSpace ,
322
327
"parameter {$ paramTagValue ->parameterName }" ,
323
- $ this ->getPhpDocLine ($ sourceNode , $ paramTagValue ),
324
328
);
325
329
$ errors = array_merge ($ errors , $ newErrors );
326
330
}
@@ -342,7 +346,9 @@ public function processVarTags(
342
346
$ errors = [];
343
347
344
348
foreach ($ varTagValues as $ varTagValue ) {
345
- foreach ($ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ varTagValue ->type ) as $ multiTypeNode ) {
349
+ $ line = $ this ->getPhpDocLine ($ originalNode , $ varTagValue );
350
+
351
+ foreach ($ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ varTagValue ->type , $ line ) as $ multiTypeNode ) {
346
352
$ identification = $ varTagValue ->variableName !== ''
347
353
? "variable {$ varTagValue ->variableName }"
348
354
: null ;
@@ -351,7 +357,6 @@ public function processVarTags(
351
357
$ multiTypeNode ,
352
358
$ nameSpace ,
353
359
$ identification ,
354
- $ this ->getPhpDocLine ($ originalNode , $ varTagValue ),
355
360
);
356
361
$ errors = array_merge ($ errors , $ newErrors );
357
362
}
@@ -373,20 +378,57 @@ public function processReturnTags(
373
378
$ errors = [];
374
379
375
380
foreach ($ returnTagValues as $ returnTagValue ) {
376
- foreach ($ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ returnTagValue ->type ) as $ multiTypeNode ) {
377
- $ newErrors = $ this ->processMultiTypePhpDocNode ($ multiTypeNode , $ nameSpace , 'return ' , $ this ->getPhpDocLine ($ originalNode , $ returnTagValue ));
381
+ $ line = $ this ->getPhpDocLine ($ originalNode , $ returnTagValue );
382
+
383
+ foreach ($ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ returnTagValue ->type , $ line ) as $ multiTypeNode ) {
384
+ $ newErrors = $ this ->processMultiTypePhpDocNode ($ multiTypeNode , $ nameSpace , 'return ' );
378
385
$ errors = array_merge ($ errors , $ newErrors );
379
386
}
380
387
}
381
388
382
389
return $ errors ;
383
390
}
384
391
392
+ /**
393
+ * @param array<ThrowsTagValueNode> $throwsTagValues
394
+ * @return list<RuleError>
395
+ */
396
+ public function processThrowsTags (
397
+ PhpParserNode $ originalNode ,
398
+ array $ throwsTagValues ,
399
+ NameScope $ nameSpace
400
+ ): array
401
+ {
402
+ $ thrownTypes = [];
403
+
404
+ foreach ($ throwsTagValues as $ throwsTagValue ) {
405
+ $ line = $ this ->getPhpDocLine ($ originalNode , $ throwsTagValue );
406
+ $ multiTypeNodes = $ this ->extractUnionAndIntersectionPhpDocTypeNodes ($ throwsTagValue ->type , $ line );
407
+
408
+ if ($ multiTypeNodes === []) {
409
+ $ innerType = $ throwsTagValue ->type ;
410
+ $ innerType ->setAttribute ('line ' , $ line );
411
+
412
+ $ thrownTypes [] = $ innerType ;
413
+ } else {
414
+ foreach ($ multiTypeNodes as $ multiTypeNode ) {
415
+ foreach ($ multiTypeNode ->types as $ typeNode ) {
416
+ $ thrownTypes [] = $ typeNode ;
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ $ unionNode = new UnionTypeNode ($ thrownTypes );
423
+ return $ this ->processMultiTypePhpDocNode ($ unionNode , $ nameSpace , 'throws ' );
424
+ }
425
+
385
426
/**
386
427
* @return list<UnionTypeNode|IntersectionTypeNode>
387
428
*/
388
- private function extractUnionAndIntersectionPhpDocTypeNodes (TypeNode $ typeNode ): array
429
+ private function extractUnionAndIntersectionPhpDocTypeNodes (TypeNode $ typeNode, int $ line ): array
389
430
{
431
+ /** @var list<UnionTypeNode|IntersectionTypeNode> $nodes */
390
432
$ nodes = [];
391
433
$ this ->traversePhpDocTypeNode ($ typeNode , static function (TypeNode $ typeNode ) use (&$ nodes ): void {
392
434
if ($ typeNode instanceof UnionTypeNode || $ typeNode instanceof IntersectionTypeNode) {
@@ -397,6 +439,13 @@ private function extractUnionAndIntersectionPhpDocTypeNodes(TypeNode $typeNode):
397
439
$ nodes [] = new UnionTypeNode ([$ typeNode ->type , new IdentifierTypeNode ('null ' )]);
398
440
}
399
441
});
442
+
443
+ foreach ($ nodes as $ node ) {
444
+ foreach ($ node ->types as $ innerType ) {
445
+ $ innerType ->setAttribute ('line ' , $ line );
446
+ }
447
+ }
448
+
400
449
return $ nodes ;
401
450
}
402
451
@@ -513,8 +562,7 @@ private function printPhpParserNode(PhpParserNode $node): string
513
562
private function processMultiTypePhpDocNode (
514
563
TypeNode $ multiTypeNode ,
515
564
NameScope $ nameSpace ,
516
- ?string $ identification ,
517
- int $ line
565
+ ?string $ identification
518
566
): array
519
567
{
520
568
$ errors = [];
@@ -525,6 +573,7 @@ private function processMultiTypePhpDocNode(
525
573
foreach ($ multiTypeNode ->types as $ type ) {
526
574
if ($ type instanceof UnionTypeNode) {
527
575
$ dnf = $ this ->typeNodeResolver ->resolve ($ multiTypeNode , $ nameSpace )->describe (VerbosityLevel::typeOnly ());
576
+ $ line = $ this ->extractLineFromPhpDocTypeNode ($ type );
528
577
529
578
$ errors [] = RuleErrorBuilder::message ("Found non-normalized type {$ multiTypeNode }{$ forWhat }: this is not disjunctive normal form, use {$ dnf }" )
530
579
->line ($ line )
@@ -544,17 +593,20 @@ private function processMultiTypePhpDocNode(
544
593
$ typeA = $ this ->typeNodeResolver ->resolve ($ typeNodeA , $ nameSpace );
545
594
$ typeB = $ this ->typeNodeResolver ->resolve ($ typeNodeB , $ nameSpace );
546
595
596
+ $ typeALine = $ this ->extractLineFromPhpDocTypeNode ($ typeNodeA );
597
+ $ typeBLine = $ this ->extractLineFromPhpDocTypeNode ($ typeNodeB );
598
+
547
599
if ($ typeA ->isSuperTypeOf ($ typeB )->yes ()) {
548
600
$ errors [] = RuleErrorBuilder::message ("Found non-normalized type {$ multiTypeNode }{$ forWhat }: {$ typeNodeB } is a subtype of {$ typeNodeA }. " )
549
- ->line ($ line )
601
+ ->line ($ typeBLine )
550
602
->identifier ('shipmonk.nonNormalizedType ' )
551
603
->build ();
552
604
continue ;
553
605
}
554
606
555
607
if ($ typeB ->isSuperTypeOf ($ typeA )->yes ()) {
556
608
$ errors [] = RuleErrorBuilder::message ("Found non-normalized type {$ multiTypeNode }{$ forWhat }: {$ typeNodeA } is a subtype of {$ typeNodeB }. " )
557
- ->line ($ line )
609
+ ->line ($ typeALine )
558
610
->identifier ('shipmonk.nonNormalizedType ' )
559
611
->build ();
560
612
}
@@ -564,6 +616,17 @@ private function processMultiTypePhpDocNode(
564
616
return $ errors ;
565
617
}
566
618
619
+ private function extractLineFromPhpDocTypeNode (TypeNode $ node ): int
620
+ {
621
+ $ line = $ node ->getAttribute ('line ' );
622
+
623
+ if (!is_int ($ line )) {
624
+ throw new LogicException ('Missing custom line attribute in node: ' . $ node );
625
+ }
626
+
627
+ return $ line ;
628
+ }
629
+
567
630
private function getPropertyNameFromNativeNode (Property $ node ): string
568
631
{
569
632
$ propertyNames = [];
0 commit comments