15
15
use Magento \SemanticVersionChecker \Operation \ClassMethodLastParameterRemoved ;
16
16
use Magento \SemanticVersionChecker \Operation \ClassMethodMoved ;
17
17
use Magento \SemanticVersionChecker \Operation \ClassMethodOptionalParameterAdded ;
18
+ use Magento \SemanticVersionChecker \Operation \ClassMethodOverwriteAdded ;
19
+ use Magento \SemanticVersionChecker \Operation \ClassMethodOverwriteRemoved ;
18
20
use Magento \SemanticVersionChecker \Operation \ClassMethodParameterTypingChanged ;
19
21
use Magento \SemanticVersionChecker \Operation \ClassMethodReturnTypingChanged ;
20
22
use Magento \SemanticVersionChecker \Operation \ExtendableClassConstructorOptionalParameterAdded ;
21
23
use Magento \SemanticVersionChecker \Operation \Visibility \MethodDecreased as VisibilityMethodDecreased ;
22
24
use Magento \SemanticVersionChecker \Operation \Visibility \MethodIncreased as VisibilityMethodIncreased ;
25
+ use PhpParser \Node \NullableType ;
23
26
use PhpParser \Node \Stmt ;
24
27
use PhpParser \Node \Stmt \ClassLike ;
25
28
use PhpParser \Node \Stmt \ClassMethod ;
26
29
use PHPSemVerChecker \Comparator \Implementation ;
27
- use PHPSemVerChecker \Comparator \Type ;
28
30
use PHPSemVerChecker \Operation \ClassMethodAdded ;
29
31
use PHPSemVerChecker \Operation \ClassMethodImplementationChanged ;
30
32
use PHPSemVerChecker \Operation \ClassMethodOperationUnary ;
35
37
use PHPSemVerChecker \Operation \ClassMethodParameterTypingRemoved ;
36
38
use PHPSemVerChecker \Operation \ClassMethodRemoved ;
37
39
use PHPSemVerChecker \Report \Report ;
40
+ use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTagNode ;
41
+ use PHPStan \PhpDocParser \Lexer \Lexer ;
42
+ use PHPStan \PhpDocParser \Parser \ConstExprParser ;
43
+ use PHPStan \PhpDocParser \Parser \PhpDocParser ;
44
+ use PHPStan \PhpDocParser \Parser \TokenIterator ;
45
+ use PHPStan \PhpDocParser \Parser \TypeParser ;
38
46
39
47
/**
40
48
* Class method analyzer.
@@ -105,6 +113,27 @@ protected function getNodeClass()
105
113
*/
106
114
protected function reportAddedNode ($ report , $ fileAfter , $ classAfter , $ methodAfter )
107
115
{
116
+ if ($ this ->dependencyGraph === null ) {
117
+ $ report ->add ($ this ->context , new ClassMethodAdded ($ this ->context , $ fileAfter , $ classAfter , $ methodAfter ));
118
+ return ;
119
+ }
120
+
121
+ $ class = $ this ->dependencyGraph ->findEntityByName ((string ) $ classAfter ->namespacedName );
122
+
123
+ if ($ class !== null ) {
124
+ foreach ($ class ->getExtends () as $ entity ) {
125
+ $ methods = $ entity ->getMethodList ();
126
+ // checks if the method is already exiting in parent class
127
+ if (isset ($ methods [$ methodAfter ->name ])) {
128
+ $ report ->add (
129
+ $ this ->context ,
130
+ new ClassMethodOverwriteAdded ($ this ->context , $ fileAfter , $ classAfter , $ methodAfter )
131
+ );
132
+ return ;
133
+ }
134
+ }
135
+ }
136
+
108
137
$ report ->add ($ this ->context , new ClassMethodAdded ($ this ->context , $ fileAfter , $ classAfter , $ methodAfter ));
109
138
}
110
139
@@ -331,12 +360,76 @@ protected function reportChanged($report, $contextBefore, $contextAfter, $method
331
360
*/
332
361
private function isReturnTypeChanged (ClassMethod $ methodBefore , ClassMethod $ methodAfter ): bool
333
362
{
334
- $ hasPHP7ReturnDeclarationChanged = !Type::isSame ($ methodBefore ->returnType , $ methodAfter ->returnType );
363
+ return $ this ->isDocBlockAnnotationReturnTypeChanged ($ methodBefore , $ methodAfter ) || $ this ->isDeclarationReturnTypeChanged ($ methodBefore , $ methodAfter );
364
+ }
365
+
366
+ /**
367
+ * @param ClassMethod $methodBefore
368
+ * @param ClassMethod $methodAfter
369
+ *
370
+ * @return bool
371
+ */
372
+ private function isDocBlockAnnotationReturnTypeChanged (ClassMethod $ methodBefore , ClassMethod $ methodAfter )
373
+ {
374
+ $ returnBefore = $ this ->getDocReturnDeclaration ($ methodBefore );
375
+ $ returnAfter = $ this ->getDocReturnDeclaration ($ methodAfter );
335
376
336
- $ returnBefore = $ this -> methodDocBlockAnalyzer -> getMethodDocDeclarationByTag ( $ methodBefore , $ this -> methodDocBlockAnalyzer :: DOC_RETURN_TAG ) ;
337
- $ returnAfter = $ this -> methodDocBlockAnalyzer -> getMethodDocDeclarationByTag ( $ methodAfter , $ this -> methodDocBlockAnalyzer :: DOC_RETURN_TAG );
377
+ return $ returnBefore !== $ returnAfter ;
378
+ }
338
379
339
- return $ hasPHP7ReturnDeclarationChanged || $ returnBefore !== $ returnAfter ;
380
+ /**
381
+ * @param ClassMethod $methodBefore
382
+ * @param ClassMethod $methodAfter
383
+ *
384
+ * @return bool
385
+ */
386
+ private function isDeclarationReturnTypeChanged (ClassMethod $ methodBefore , ClassMethod $ methodAfter )
387
+ {
388
+ if (!$ this ->isReturnsEqualByNullability ($ methodBefore , $ methodAfter )) {
389
+ return true ;
390
+ }
391
+ $ beforeMethodReturnType = $ methodBefore ->returnType instanceof NullableType ? (string ) $ methodBefore ->returnType ->type : (string ) $ methodBefore ->returnType ;
392
+ $ afterMethodReturnType = $ methodAfter ->returnType instanceof NullableType ? (string ) $ methodAfter ->returnType ->type : (string ) $ methodAfter ->returnType ;
393
+
394
+ return $ beforeMethodReturnType !== $ afterMethodReturnType ;
395
+ }
396
+
397
+ /**
398
+ * checks if both return types has same nullable status
399
+ *
400
+ * @param ClassMethod $before
401
+ * @param ClassMethod $after
402
+ *
403
+ * @return bool
404
+ */
405
+ private function isReturnsEqualByNullability (ClassMethod $ before , ClassMethod $ after ): bool
406
+ {
407
+ return ($ before instanceof NullableType) === ($ after instanceof NullableType);
408
+ }
409
+
410
+ /**
411
+ * Analyses the Method doc block and returns the return type declaration
412
+ *
413
+ * @param ClassMethod $method
414
+ *
415
+ * @return string
416
+ */
417
+ private function getDocReturnDeclaration (ClassMethod $ method )
418
+ {
419
+ if ($ method ->getDocComment () !== null ) {
420
+ $ lexer = new Lexer ();
421
+ $ typeParser = new TypeParser ();
422
+ $ constExprParser = new ConstExprParser ();
423
+ $ phpDocParser = new PhpDocParser ($ typeParser , $ constExprParser );
424
+
425
+ $ tokens = $ lexer ->tokenize ((string )$ method ->getDocComment ());
426
+ $ tokenIterator = new TokenIterator ($ tokens );
427
+ $ phpDocNode = $ phpDocParser ->parse ($ tokenIterator );
428
+ $ tags = $ phpDocNode ->getTagsByName ('@return ' );
429
+ /** @var PhpDocTagNode $tag */
430
+ $ tag = array_shift ($ tags );
431
+ }
432
+ return isset ($ tag ) ? (string )$ tag ->value : ' ' ;
340
433
}
341
434
342
435
/**
@@ -415,4 +508,4 @@ private function analyzeRemainingMethodParams($contextAfter, $methodAfter, $rema
415
508
416
509
return $ data ;
417
510
}
418
- }
511
+ }
0 commit comments