From 6710b31a2c4777d0ef4aa8663c0222f1e32258e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bundyra?= Date: Sun, 17 Nov 2019 20:37:30 +0000 Subject: [PATCH] Add Iterator type support + fixes for functions with yield Generators (functions with yield) can have only Generator, Iterator, Traversable or iterable Return type hint. Add support for Iterator type in `Functions\Param` and `Functions\ReturnType` sniffs. --- .../Sniffs/Functions/ParamSniff.php | 29 +++++++- .../Sniffs/Functions/ReturnTypeSniff.php | 66 +++++++++++++++++++ test/Sniffs/Functions/ParamUnitTest.1.inc | 5 ++ test/Sniffs/Functions/ParamUnitTest.php | 1 + .../Sniffs/Functions/ReturnTypeUnitTest.2.inc | 13 ++++ .../Functions/ReturnTypeUnitTest.2.inc.fixed | 13 ++++ .../Sniffs/Functions/ReturnTypeUnitTest.3.inc | 52 +++++++++++++++ test/Sniffs/Functions/ReturnTypeUnitTest.php | 24 ++++--- 8 files changed, 191 insertions(+), 12 deletions(-) diff --git a/src/WebimpressCodingStandard/Sniffs/Functions/ParamSniff.php b/src/WebimpressCodingStandard/Sniffs/Functions/ParamSniff.php index b1bb195b..0a31f68a 100644 --- a/src/WebimpressCodingStandard/Sniffs/Functions/ParamSniff.php +++ b/src/WebimpressCodingStandard/Sniffs/Functions/ParamSniff.php @@ -331,6 +331,7 @@ private function checkParam( 'iterable' => ['iterable'], 'traversable' => ['traversable', '\traversable'], 'generator' => ['generator', '\generator'], + 'iterator' => ['iterator', '\iterator'], 'object' => ['object'], ]; // @phpcs:enable @@ -444,7 +445,7 @@ private function checkParam( '?\generator', ], true) && ! in_array($lower, ['generator', '\generator'], true) - && in_array($lower, array_merge($simpleTypes, ['mixed']), true) + && in_array($lower, $simpleTypes, true) ) { $error = 'Param type contains %s which is not a generator type'; $data = [ @@ -456,10 +457,30 @@ private function checkParam( continue; } + // iterator + if (in_array($lowerTypeHint, [ + 'iterator', + '?iterator', + '\iterator', + '?\iterator', + ], true) + && ! in_array($lower, ['iterator', '\iterator'], true) + && in_array($lower, $simpleTypes, true) + ) { + $error = 'Param type contains %s which is not an Iterator type'; + $data = [ + $type, + ]; + $phpcsFile->addError($error, $tagPtr + 2, 'NotIteratorType', $data); + + $break = true; + continue; + } + // object if (in_array($lowerTypeHint, ['object', '?object'], true) && $lower !== 'object' - && (in_array($lower, array_merge($simpleTypes, ['mixed']), true) + && (in_array($lower, $simpleTypes, true) || strpos($type, '[]') !== false) ) { $error = 'Param type contains %s which is not an object type'; @@ -485,6 +506,10 @@ private function checkParam( '?generator', '\generator', '?\generator', + 'iterator', + '?iterator', + '\iterator', + '?\iterator', 'object', '?object', ]; diff --git a/src/WebimpressCodingStandard/Sniffs/Functions/ReturnTypeSniff.php b/src/WebimpressCodingStandard/Sniffs/Functions/ReturnTypeSniff.php index 872a61e2..cd501bad 100644 --- a/src/WebimpressCodingStandard/Sniffs/Functions/ReturnTypeSniff.php +++ b/src/WebimpressCodingStandard/Sniffs/Functions/ReturnTypeSniff.php @@ -4,9 +4,12 @@ namespace WebimpressCodingStandard\Sniffs\Functions; +use Generator; +use Iterator; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Tokens; +use Traversable; use WebimpressCodingStandard\Helper\MethodsTrait; use function array_filter; @@ -19,6 +22,7 @@ use function explode; use function implode; use function in_array; +use function ltrim; use function preg_grep; use function preg_replace; use function preg_split; @@ -333,6 +337,10 @@ private function processReturnType(File $phpcsFile, int $stackPtr) : void '?generator', '\generator', '?\generator', + 'iterator', + '?iterator', + '\iterator', + '?\iterator', 'object', '?object', ]; @@ -426,6 +434,7 @@ private function processReturnType(File $phpcsFile, int $stackPtr) : void 'iterable' => ['iterable'], 'traversable' => ['traversable', '\traversable'], 'generator' => ['generator', '\generator'], + 'iterator' => ['iterator', '\iterator'], 'object' => ['object'], ]; @@ -491,6 +500,26 @@ private function processReturnType(File $phpcsFile, int $stackPtr) : void } break; + case 'iterator': + case '?iterator': + case '\iterator': + case '?\iterator': + foreach ($this->returnDocTypes as $type) { + $lower = strtolower($type); + if (in_array($lower, ['iterator', '\iterator'], true)) { + continue; + } + + if (in_array($lower, $simpleTypes, true)) { + $error = 'Return type contains "%s" which is not an Iterator type'; + $data = [ + $type, + ]; + $phpcsFile->addError($error, $this->returnDoc + 2, 'NotIteratorType', $data); + } + } + break; + case 'traversable': case '?traversable': case '\traversable': @@ -599,11 +628,48 @@ private function processReturnStatements(File $phpcsFile, int $stackPtr) : void continue; } + $isYield = $tokens[$i]['code'] !== T_RETURN; + if ($isYield && $this->returnType && $this->returnTypeIsValid) { + $type = strtolower(ltrim($this->returnTypeValue, '?')); + + if ($type !== 'iterable' + && ((! isset($this->importedClasses[$type]['fqn']) + && ! in_array(ltrim($type, '\\'), [ + 'generator', + 'iterator', + 'traversable', + ], true)) + || (isset($this->importedClasses[$type]['fqn']) + && ! in_array($this->importedClasses[$type]['fqn'], [ + Generator::class, + Iterator::class, + Traversable::class, + ], true))) + ) { + $phpcsFile->addError( + sprintf( + 'Generators may only declare a return type of Generator, Iterator, Traversable,' + . ' or iterable, %s is not permitted', + $this->returnTypeValue + ), + $this->returnType, + 'InvalidGeneratorType' + ); + + return; + } + } + $next = $phpcsFile->findNext(Tokens::$emptyTokens, $i + 1, null, true); if ($tokens[$next]['code'] === T_SEMICOLON) { $this->returnCodeVoid($phpcsFile, $i); } else { $this->returnCodeValue($phpcsFile, $i); + + if ($isYield) { + return; + } + $returnValues[$next] = $this->getReturnValue($phpcsFile, $next); if ($this->returnDoc diff --git a/test/Sniffs/Functions/ParamUnitTest.1.inc b/test/Sniffs/Functions/ParamUnitTest.1.inc index d4581505..157e5a3d 100644 --- a/test/Sniffs/Functions/ParamUnitTest.1.inc +++ b/test/Sniffs/Functions/ParamUnitTest.1.inc @@ -196,4 +196,9 @@ class FunctionParam object $static, object $parent ) : void; + + /** + * @param int|array|string|bool[]|float[]|MyIterator|\Iterator $iterator + */ + abstract protected function nonIteratorTypes(\Iterator $iterator) : void; } diff --git a/test/Sniffs/Functions/ParamUnitTest.php b/test/Sniffs/Functions/ParamUnitTest.php index 3d292d84..c8a3a2f7 100644 --- a/test/Sniffs/Functions/ParamUnitTest.php +++ b/test/Sniffs/Functions/ParamUnitTest.php @@ -30,6 +30,7 @@ protected function getErrorList(string $testFile = '') : array 143 => 2, 154 => 1, 159 => 1, + 201 => 4, ]; } diff --git a/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc b/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc index b3f381bb..69bf4257 100644 --- a/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc +++ b/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc @@ -471,4 +471,17 @@ abstract class FunctionCommentReturn { return $this; } + + /** + * @return int[]|float[]|string|array|bool|MyClass|MyIterator|\Iterator + */ + public function generatorIterator() : \Iterator + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } } diff --git a/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc.fixed b/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc.fixed index 134386c9..c121f29d 100644 --- a/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc.fixed +++ b/test/Sniffs/Functions/ReturnTypeUnitTest.2.inc.fixed @@ -471,4 +471,17 @@ abstract class FunctionCommentReturn { return $this; } + + /** + * @return int[]|float[]|string|array|bool|MyClass|MyIterator + */ + public function generatorIterator() : \Iterator + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } } diff --git a/test/Sniffs/Functions/ReturnTypeUnitTest.3.inc b/test/Sniffs/Functions/ReturnTypeUnitTest.3.inc index 5eadd98e..b96dd2ba 100644 --- a/test/Sniffs/Functions/ReturnTypeUnitTest.3.inc +++ b/test/Sniffs/Functions/ReturnTypeUnitTest.3.inc @@ -2,6 +2,8 @@ namespace MyNamespace\Test\Functions; +use Iterator as IteratorAlias; + class FunctionCommentReturn { /** @@ -182,4 +184,54 @@ class FunctionCommentReturn { return []; } + + public function generator1() : iterable + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } + + public function generator2() : \Generator + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } + + public function generator3() : \Traversable + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } + + public function generator4() : IteratorAlias + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } + + public function generator5() : MyCustomGenerator + { + yield 1; + yield 1.2; + yield 'string'; + yield []; + yield true; + yield null; + } } diff --git a/test/Sniffs/Functions/ReturnTypeUnitTest.php b/test/Sniffs/Functions/ReturnTypeUnitTest.php index 67e6923d..4fd6c02e 100644 --- a/test/Sniffs/Functions/ReturnTypeUnitTest.php +++ b/test/Sniffs/Functions/ReturnTypeUnitTest.php @@ -153,19 +153,21 @@ protected function getErrorList(string $testFile = '') : array 452 => 1, 460 => 1, 468 => 1, + 476 => 4, ]; case 'ReturnTypeUnitTest.3.inc': return [ - 8 => 1, - 24 => 1, - 40 => 1, - 56 => 1, - 72 => 1, - 96 => 1, - 120 => 1, - 144 => 1, - 160 => 1, - 168 => 1, + 10 => 1, + 26 => 1, + 42 => 1, + 58 => 1, + 74 => 1, + 98 => 1, + 122 => 1, + 146 => 1, + 162 => 1, + 170 => 1, + 228 => 1, ]; } @@ -180,6 +182,8 @@ protected function getErrorList(string $testFile = '') : array 36 => 1, 41 => 1, 46 => 1, + 54 => 1, + 59 => 1, 95 => 1, 99 => 1, 108 => 1,