1515use Magento \SemanticVersionChecker \Operation \ClassMethodLastParameterRemoved ;
1616use Magento \SemanticVersionChecker \Operation \ClassMethodMoved ;
1717use Magento \SemanticVersionChecker \Operation \ClassMethodOptionalParameterAdded ;
18+ use Magento \SemanticVersionChecker \Operation \ClassMethodOverwriteAdded ;
19+ use Magento \SemanticVersionChecker \Operation \ClassMethodOverwriteRemoved ;
1820use Magento \SemanticVersionChecker \Operation \ClassMethodParameterTypingChanged ;
1921use Magento \SemanticVersionChecker \Operation \ClassMethodReturnTypingChanged ;
2022use Magento \SemanticVersionChecker \Operation \ExtendableClassConstructorOptionalParameterAdded ;
2123use Magento \SemanticVersionChecker \Operation \Visibility \MethodDecreased as VisibilityMethodDecreased ;
2224use Magento \SemanticVersionChecker \Operation \Visibility \MethodIncreased as VisibilityMethodIncreased ;
25+ use PhpParser \Node \NullableType ;
2326use PhpParser \Node \Stmt ;
2427use PhpParser \Node \Stmt \ClassLike ;
2528use PhpParser \Node \Stmt \ClassMethod ;
2629use PHPSemVerChecker \Comparator \Implementation ;
27- use PHPSemVerChecker \Comparator \Type ;
2830use PHPSemVerChecker \Operation \ClassMethodAdded ;
2931use PHPSemVerChecker \Operation \ClassMethodImplementationChanged ;
3032use PHPSemVerChecker \Operation \ClassMethodOperationUnary ;
3537use PHPSemVerChecker \Operation \ClassMethodParameterTypingRemoved ;
3638use PHPSemVerChecker \Operation \ClassMethodRemoved ;
3739use 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 ;
3846
3947/**
4048 * Class method analyzer.
@@ -105,6 +113,27 @@ protected function getNodeClass()
105113 */
106114 protected function reportAddedNode ($ report , $ fileAfter , $ classAfter , $ methodAfter )
107115 {
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+
108137 $ report ->add ($ this ->context , new ClassMethodAdded ($ this ->context , $ fileAfter , $ classAfter , $ methodAfter ));
109138 }
110139
@@ -331,12 +360,76 @@ protected function reportChanged($report, $contextBefore, $contextAfter, $method
331360 */
332361 private function isReturnTypeChanged (ClassMethod $ methodBefore , ClassMethod $ methodAfter ): bool
333362 {
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 );
335376
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+ }
338379
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 : ' ' ;
340433 }
341434
342435 /**
@@ -415,4 +508,4 @@ private function analyzeRemainingMethodParams($contextAfter, $methodAfter, $rema
415508
416509 return $ data ;
417510 }
418- }
511+ }
0 commit comments