1717
1818use PHP_CodeSniffer \Exceptions \RuntimeException as CodeSnifferRuntimeException ;
1919use PHP_CodeSniffer \Files \File ;
20+ use PHP_CodeSniffer \Util \Tokens ;
2021
2122/**
2223 * @package php-coding-standards
@@ -437,7 +438,7 @@ public static function countReturns(File $file, int $functionPosition): array
437438 return [0 , 0 ];
438439 }
439440
440- $ nonVoidReturnCount = $ voidReturnCount = 0 ;
441+ $ nonVoidReturnCount = $ voidReturnCount = $ nullReturnCount = 0 ;
441442 $ scopeClosers = new \SplStack ();
442443 $ tokens = $ file ->getTokens ();
443444 for ($ i = $ functionStart + 1 ; $ i < $ functionEnd ; $ i ++) {
@@ -452,10 +453,11 @@ public static function countReturns(File $file, int $functionPosition): array
452453
453454 if (!$ scopeClosers ->count () && $ tokens [$ i ]['code ' ] === T_RETURN ) {
454455 Helpers::isVoidReturn ($ file , $ i ) ? $ voidReturnCount ++ : $ nonVoidReturnCount ++;
456+ Helpers::isNullReturn ($ file , $ i ) and $ nullReturnCount ++;
455457 }
456458 }
457459
458- return [$ nonVoidReturnCount , $ voidReturnCount ];
460+ return [$ nonVoidReturnCount , $ voidReturnCount, $ nullReturnCount ];
459461 }
460462
461463 /**
@@ -473,7 +475,88 @@ public static function isVoidReturn(File $file, int $returnPosition): bool
473475
474476 $ returnPosition ++;
475477 $ nextToReturn = $ file ->findNext ([T_WHITESPACE ], $ returnPosition , null , true , null , true );
478+ $ nextToReturnType = $ tokens [$ nextToReturn ]['code ' ] ?? '' ;
476479
477- return $ nextToReturn && ($ tokens [$ nextToReturn ]['type ' ] ?? '' ) === 'T_SEMICOLON ' ;
480+ return in_array ($ nextToReturnType , [T_SEMICOLON , T_NULL ], true );
481+ }
482+
483+ /**
484+ * @param File $file
485+ * @param int $returnPosition
486+ * @return bool
487+ */
488+ public static function isNullReturn (File $ file , int $ returnPosition ): bool
489+ {
490+ $ tokens = $ file ->getTokens ();
491+
492+ if (($ tokens [$ returnPosition ]['code ' ] ?? '' ) !== T_RETURN ) {
493+ return false ;
494+ }
495+
496+ $ returnPosition ++;
497+ $ nextToReturn = $ file ->findNext ([T_WHITESPACE ], $ returnPosition , null , true , null , true );
498+ $ nextToReturnType = $ tokens [$ nextToReturn ]['code ' ] ?? '' ;
499+
500+ return $ nextToReturnType === T_NULL ;
501+ }
502+
503+ /**
504+ * @param string $tag
505+ * @param File $file
506+ * @param int $functionPosition
507+ * @return string[]
508+ */
509+ public static function functionDocBlockTag (
510+ string $ tag ,
511+ File $ file ,
512+ int $ functionPosition
513+ ): array {
514+
515+ $ tokens = $ file ->getTokens ();
516+ if (!array_key_exists ($ functionPosition , $ tokens )
517+ || !in_array ($ tokens [$ functionPosition ]['code ' ], [T_FUNCTION , T_CLOSURE ], true )
518+ ) {
519+ return [];
520+ }
521+
522+ $ exclude = array_values (Tokens::$ methodPrefixes );
523+ $ exclude [] = T_WHITESPACE ;
524+
525+ $ lastBeforeFunc = $ file ->findPrevious ($ exclude , $ functionPosition - 1 , null , true );
526+
527+ if (!$ lastBeforeFunc
528+ || !array_key_exists ($ lastBeforeFunc , $ tokens )
529+ || $ tokens [$ lastBeforeFunc ]['code ' ] !== T_DOC_COMMENT_CLOSE_TAG
530+ || empty ($ tokens [$ lastBeforeFunc ]['comment_opener ' ])
531+ || $ tokens [$ lastBeforeFunc ]['comment_opener ' ] >= $ lastBeforeFunc
532+ ) {
533+ return [];
534+ }
535+
536+ $ tags = [];
537+ $ inTag = false ;
538+ $ start = $ tokens [$ lastBeforeFunc ]['comment_opener ' ] + 1 ;
539+ $ end = $ lastBeforeFunc - 1 ;
540+
541+ for ($ i = $ start ; $ i < $ end ; $ i ++) {
542+
543+ if ($ inTag && $ tokens [$ i ]['code ' ] === T_DOC_COMMENT_STRING ) {
544+ $ tags [] .= $ tokens [$ i ]['content ' ];
545+ continue ;
546+ }
547+
548+ if ($ inTag && $ tokens [$ i ]['code ' ] !== T_DOC_COMMENT_WHITESPACE ) {
549+ $ inTag = false ;
550+ continue ;
551+ }
552+
553+ if ($ tokens [$ i ]['code ' ] === T_DOC_COMMENT_TAG
554+ && (ltrim ($ tokens [$ i ]['content ' ], '@ ' ) === ltrim ($ tag , '@ ' ))
555+ ) {
556+ $ inTag = true ;
557+ }
558+ }
559+
560+ return $ tags ;
478561 }
479562}
0 commit comments