From 6f90b6233b71d165e1134b314d867c14d2ddfbda Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Dec 2019 19:02:13 +0100 Subject: [PATCH 1/7] :sparkles: null at last --- .../Sniffs/NamingConventions/ValidTypeHintSniff.php | 13 +++++++++++++ .../NamingConventions/ValidTypeHintUnitTest.inc | 6 ++++++ .../ValidTypeHintUnitTest.inc.fixed | 6 ++++++ .../NamingConventions/ValidTypeHintUnitTest.php | 3 +++ 4 files changed, 28 insertions(+) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index 01b191f..3a3c57a 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -141,6 +141,19 @@ private function getValidTypes(string $content): string $types[$index] = $validType; } + $types = array_unique($types); + usort($types, function ($type1, $type2) { + if ('null' === $type1) { + return 1; + } + + if ('null' === $type2) { + return -1; + } + + return 0; + }); + return implode('|', $types); } diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index 4dd4b3c..339ea4b 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -45,3 +45,9 @@ echo ( float ) $a; /** @method integer|string */ /** @method array|integer[]|array truc */ /** @method array|integer[]|array> truc */ + +/** + * @return null|int + * @return string[]|null|int[] + * @return Foo|null|int[] + */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed index b1fbe31..64ad474 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed @@ -45,3 +45,9 @@ echo ( float ) $a; /** @method int|string */ /** @method array|int[]|array truc */ /** @method array|int[]|array> truc */ + +/** + * @return int|null + * @return string[]|int[]|null + * @return Foo|int[]|null + */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php index 3fa9aa7..65a3827 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php @@ -37,6 +37,9 @@ protected function getErrorList(): array 45 => 1, 46 => 1, 47 => 1, + 50 => 1, + 51 => 1, + 52 => 1, ]; } From b178c7d125fdb5faad9690284dfcc2cf0dc662d0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 27 Dec 2019 00:46:41 +0100 Subject: [PATCH 2/7] :sparkles: Handle space --- .../NamingConventions/ValidTypeHintSniff.php | 79 +++++++++---------- .../ValidTypeHintUnitTest.inc | 2 + .../ValidTypeHintUnitTest.inc.fixed | 2 + .../ValidTypeHintUnitTest.php | 1 + 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index 3a3c57a..5e4664d 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -14,13 +14,7 @@ */ class ValidTypeHintSniff implements Sniff { - private const TEXT = '[\\\\a-z0-9]'; - private const OPENER = '\<|\[|\{|\('; - private const MIDDLE = '\,|\:|\=\>'; - private const CLOSER = '\>|\]|\}|\)'; - private const SEPARATOR = '\&|\|'; - - /* + /** * is any non-array, non-generic, non-alternated type, eg `int` or `\Foo` * is array of , eg `int[]` or `\Foo[]` * is generic collection type, like `array`, `Collection` and more complex like `Collection>` @@ -30,24 +24,24 @@ class ValidTypeHintSniff implements Sniff private const REGEX_TYPES = ' (? (? - (? - (?&simple)(\[\])* - ) - | - (? - [@$?]?[\\\\\w]+ - ) - | (? - (?(?&simple)) - < - (?:(?(?&types)),\s*)?(?(?&types)|(?&generic)) + (?(?&simple))\s* + <\s* + (?: + (?(?&types))\s* + ,\s* + )? + (?(?&types)|(?&generic))\s* > ) + | + (?(?&simple)(\s*\[\s*\])+) + | + (?[@$?]?[\\\\\w]+) ) (?: - \| - (?:(?&simple)|(?&array)|(?&generic)) + \s*\|\s* + (?:(?&generic)|(?&array)|(?&simple)) )* ) '; @@ -76,7 +70,7 @@ public function process(File $phpcsFile, $stackPtr): void ); $content = 1 === $matchingResult ? $matches['types'] : ''; - $endOfContent = preg_replace('/'.preg_quote($content, '/').'/', '', $tokens[$stackPtr + 2]['content'], 1); + $endOfContent = substr($tokens[$stackPtr + 2]['content'], strlen($content)); $suggestedType = $this->getValidTypes($content); @@ -95,24 +89,6 @@ public function process(File $phpcsFile, $stackPtr): void } } - /** - * @param string $content - * - * @return array - */ - private function getTypes(string $content): array - { - $types = []; - while ('' !== $content && false !== $content) { - preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches); - - $types[] = $matches['type']; - $content = substr($content, strlen($matches['type']) + 1); - } - - return $types; - } - /** * @param string $content * @@ -120,13 +96,12 @@ private function getTypes(string $content): array */ private function getValidTypes(string $content): string { + $content = preg_replace('/\s/', '', $content); $types = $this->getTypes($content); - foreach ($types as $index => $type) { - $type = str_replace(' ', '', $type); - preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches); - if (isset($matches['generic'])) { + + if (isset($matches['generic']) && '' !== $matches['generic']) { $validType = $this->getValidType($matches['genericName']).'<'; if ('' !== $matches['genericKey']) { @@ -157,6 +132,24 @@ private function getValidTypes(string $content): string return implode('|', $types); } + /** + * @param string $content + * + * @return array + */ + private function getTypes(string $content): array + { + $types = []; + while ('' !== $content && false !== $content) { + preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches); + + $types[] = $matches['type']; + $content = substr($content, strlen($matches['type']) + 1); + } + + return $types; + } + /** * @param string $typeName * diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index 339ea4b..ffc0388 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -51,3 +51,5 @@ echo ( float ) $a; * @return string[]|null|int[] * @return Foo|null|int[] */ + +/** @method array < integer , null | boolean > | integer [ ] | array < array < integer > > truc */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed index 64ad474..5bf39d3 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed @@ -51,3 +51,5 @@ echo ( float ) $a; * @return string[]|int[]|null * @return Foo|int[]|null */ + +/** @method array|int[]|array> truc */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php index 65a3827..bacf4b9 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php @@ -40,6 +40,7 @@ protected function getErrorList(): array 50 => 1, 51 => 1, 52 => 1, + 55 => 1, ]; } From 9939c03bf1d285fb2724ce0bf7a6a9af7d157f75 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 27 Dec 2019 01:06:09 +0100 Subject: [PATCH 3/7] :sparkles: Add better support for generic --- .../NamingConventions/ValidTypeHintSniff.php | 114 ++++++++++++++---- .../ValidTypeHintUnitTest.inc | 10 ++ .../ValidTypeHintUnitTest.inc.fixed | 10 ++ .../ValidTypeHintUnitTest.php | 7 ++ 4 files changed, 120 insertions(+), 21 deletions(-) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index 5e4664d..d2052e3 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -17,31 +17,60 @@ class ValidTypeHintSniff implements Sniff /** * is any non-array, non-generic, non-alternated type, eg `int` or `\Foo` * is array of , eg `int[]` or `\Foo[]` - * is generic collection type, like `array`, `Collection` and more complex like `Collection>` - * is , or type, like `int`, `bool[]` or `Collection` + * is generic collection type, like `array`, `Collection` or more complex` + * is array key => value type, like `array{type: string, name: string, value: mixed}` + * is Foo::class type, like `class-string` or `class-string` + * is , , , or type * is one or more types alternated via `|`, like `int|bool[]|Collection` */ private const REGEX_TYPES = ' (? (? (? - (?(?&simple))\s* - <\s* - (?: - (?(?&types))\s* - ,\s* - )? - (?(?&types)|(?&generic))\s* - > + (? + (?&simple) + ) + \s*<\s* + (? + (?:(?&types)\s*,\s*)* + (?&types) + ) + \s*> ) | - (?(?&simple)(\s*\[\s*\])+) + (? + array\s*{\s* + (? + (?: + (? + (?:\w+\s*\??:\s*)? + (?&types) + ) + \s*,\s* + )* + (?&objectKeyValue) + ) + \s*} + ) + | + (? + (?&simple)(?: + \s*\[\s*\] + )+ + ) + | + (? + class-string(?: + \s*<\s*[\\\\\w]+\s*> + )? + ) | - (?[@$?]?[\\\\\w]+) + (? + [@$?]?[\\\\\w]+ + ) ) (?: - \s*\|\s* - (?:(?&generic)|(?&array)|(?&simple)) + \s*\|\s*(?&type) )* ) '; @@ -98,17 +127,14 @@ private function getValidTypes(string $content): string { $content = preg_replace('/\s/', '', $content); $types = $this->getTypes($content); + foreach ($types as $index => $type) { preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches); if (isset($matches['generic']) && '' !== $matches['generic']) { - $validType = $this->getValidType($matches['genericName']).'<'; - - if ('' !== $matches['genericKey']) { - $validType .= $this->getValidTypes($matches['genericKey']).', '; - } - - $validType .= $this->getValidTypes($matches['genericValue']).'>'; + $validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']); + } elseif (isset($matches['object']) && '' !== $matches['object']) { + $validType = $this->getValidObjectType($matches['objectContent']); } else { $validType = $this->getValidType($type); } @@ -150,6 +176,52 @@ private function getTypes(string $content): array return $types; } + /** + * @param string $genericName + * @param string $genericContent + * + * @return string + */ + private function getValidGenericType(string $genericName, string $genericContent): string + { + $validType = $this->getValidType($genericName).'<'; + + while ('' !== $genericContent && false !== $genericContent) { + preg_match('{^'.self::REGEX_TYPES.',?}x', $genericContent, $matches); + + $validType .= $this->getValidTypes($matches['types']).', '; + $genericContent = substr($genericContent, strlen($matches['types']) + 1); + } + + return preg_replace('/,\s$/', '>', $validType); + } + + /** + * @param string $objectContent + * + * @return string + */ + private function getValidObjectType(string $objectContent): string + { + $validType = 'array{'; + + while ('' !== $objectContent && false !== $objectContent) { + $split = preg_split('/(\??:|,)/', $objectContent, 2, PREG_SPLIT_DELIM_CAPTURE); + + if (isset($split[1]) && ',' !== $split[1]) { + $validType .= $split[0].$split[1].' '; + $objectContent = $split[2]; + } + + preg_match('{^'.self::REGEX_TYPES.',?}x', $objectContent, $matches); + + $validType .= $this->getValidTypes($matches['types']).', '; + $objectContent = substr($objectContent, strlen($matches['types']) + 1); + } + + return preg_replace('/,\s$/', '}', $validType); + } + /** * @param string $typeName * diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index ffc0388..1cde4a6 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -53,3 +53,13 @@ echo ( float ) $a; */ /** @method array < integer , null | boolean > | integer [ ] | array < array < integer > > truc */ +/** @method Generator truc */ +/** @method Generator, array, array, array> truc */ +/** @method array { integer: integer, boolean?: boolean } truc */ +/** @param class-string|class-string|integer truc */ + +/** + * @param array{0: integer, 1?: integer} + * @param array{integer, integer} + * @param array{foo: integer, bar: string} + */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed index 5bf39d3..fb4e57c 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed @@ -53,3 +53,13 @@ echo ( float ) $a; */ /** @method array|int[]|array> truc */ +/** @method Generator truc */ +/** @method Generator, array, array, array> truc */ +/** @method array{integer: int, boolean?: bool} truc */ +/** @param class-string|class-string|int truc */ + +/** + * @param array{0: int, 1?: int} + * @param array{int, int} + * @param array{foo: int, bar: string} + */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php index bacf4b9..0f5e760 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php @@ -41,6 +41,13 @@ protected function getErrorList(): array 51 => 1, 52 => 1, 55 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 59 => 1, + 62 => 1, + 63 => 1, + 64 => 1, ]; } From e56dc8548abc4bb7320a7f94c3adf89d0aec00f7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 29 Dec 2019 00:39:50 +0100 Subject: [PATCH 4/7] :sparkles: Handle more type definitions --- .../NamingConventions/ValidTypeHintSniff.php | 192 +++++++++++------- .../ValidTypeHintUnitTest.inc | 25 +-- .../ValidTypeHintUnitTest.inc.fixed | 25 +-- .../ValidTypeHintUnitTest.php | 37 ++-- 4 files changed, 145 insertions(+), 134 deletions(-) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index d2052e3..c92a37f 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -4,6 +4,7 @@ namespace SymfonyCustom\Sniffs\NamingConventions; +use PHP_CodeSniffer\Exceptions\DeepExitException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Common; @@ -26,51 +27,59 @@ class ValidTypeHintSniff implements Sniff private const REGEX_TYPES = ' (? (? - (? - (? - (?&simple) - ) - \s*<\s* - (? - (?:(?&types)\s*,\s*)* - (?&types) - ) - \s*> - ) - | - (? - array\s*{\s* - (? - (?: - (? - (?:\w+\s*\??:\s*)? - (?&types) - ) - \s*,\s* - )* - (?&objectKeyValue) - ) - \s*} - ) - | (? - (?&simple)(?: + (?¬Array)(?: \s*\[\s*\] )+ ) | - (? - class-string(?: - \s*<\s*[\\\\\w]+\s*> - )? - ) - | - (? - [@$?]?[\\\\\w]+ + (? + (? + \(\s*(? + (?&types) + )\s*\) + ) + | + (? + (? + (?&simple) + ) + \s*<\s* + (? + (?:(?&types)\s*,\s*)* + (?&types) + ) + \s*> + ) + | + (? + array\s*{\s* + (? + (?: + (? + (?:\w+\s*\??:\s*)? + (?&types) + ) + \s*,\s* + )* + (?&objectKeyValue) + ) + \s*} + ) + | + (? + class-string(?: + \s*<\s*[\\\\\w]+\s*> + )? + ) + | + (? + [@$?]?[\\\\\w]+ + ) ) ) (?: - \s*\|\s*(?&type) + \s*[\|&]\s*(?&type) )* ) '; @@ -91,29 +100,41 @@ public function process(File $phpcsFile, $stackPtr): void { $tokens = $phpcsFile->getTokens(); - if (in_array($tokens[$stackPtr]['content'], SniffHelper::TAGS_WITH_TYPE)) { - $matchingResult = preg_match( - '{^'.self::REGEX_TYPES.'(?:[ \t].*)?$}sx', - $tokens[$stackPtr + 2]['content'], - $matches - ); + if (!in_array($tokens[$stackPtr]['content'], SniffHelper::TAGS_WITH_TYPE)) { + return; + } + + $matchingResult = preg_match( + '{^'.self::REGEX_TYPES.'(?:[\s\t].*)?$}sx', + $tokens[$stackPtr + 2]['content'], + $matches + ); - $content = 1 === $matchingResult ? $matches['types'] : ''; - $endOfContent = substr($tokens[$stackPtr + 2]['content'], strlen($content)); + $content = 1 === $matchingResult ? $matches['types'] : ''; + $endOfContent = substr($tokens[$stackPtr + 2]['content'], strlen($content)); + try { $suggestedType = $this->getValidTypes($content); + } catch (DeepExitException $exception) { + $phpcsFile->addError( + $exception->getMessage(), + $stackPtr + 2, + 'Exception' + ); - if ($content !== $suggestedType) { - $fix = $phpcsFile->addFixableError( - 'For type-hinting in PHPDocs, use %s instead of %s', - $stackPtr + 2, - 'Invalid', - [$suggestedType, $content] - ); + return; + } + + if ($content !== $suggestedType) { + $fix = $phpcsFile->addFixableError( + 'For type-hinting in PHPDocs, use %s instead of %s', + $stackPtr + 2, + 'Invalid', + [$suggestedType, $content] + ); - if ($fix) { - $phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent); - } + if ($fix) { + $phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent); } } } @@ -122,26 +143,57 @@ public function process(File $phpcsFile, $stackPtr): void * @param string $content * * @return string + * + * @throws DeepExitException */ private function getValidTypes(string $content): string { $content = preg_replace('/\s/', '', $content); - $types = $this->getTypes($content); - foreach ($types as $index => $type) { - preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches); + $types = []; + $separators = []; + while ('' !== $content && false !== $content) { + preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches); - if (isset($matches['generic']) && '' !== $matches['generic']) { + if (isset($matches['multiple']) && '' !== $matches['multiple']) { + $validType = '('.$this->getValidTypes($matches['mutipleContent']).')'; + } elseif (isset($matches['generic']) && '' !== $matches['generic']) { $validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']); } elseif (isset($matches['object']) && '' !== $matches['object']) { $validType = $this->getValidObjectType($matches['objectContent']); } else { - $validType = $this->getValidType($type); + $validType = $this->getValidType($matches['type']); } - $types[$index] = $validType; + $types[] = $validType; + + $separators[] = substr($content, strlen($matches['type']), 1); + $content = substr($content, strlen($matches['type']) + 1); } + // Remove last separator since it's an empty string + array_pop($separators); + + $uniqueSeparators = array_unique($separators); + switch (count($uniqueSeparators)) { + case 0: + return implode('', $types); + case 1: + return implode($uniqueSeparators[0], $this->orderTypes($types)); + default: + throw new DeepExitException( + 'Union and intersection types must be grouped with parenthesis when used in the same expression' + ); + } + } + + /** + * @param array $types + * + * @return array + */ + private function orderTypes(array $types): array + { $types = array_unique($types); usort($types, function ($type1, $type2) { if ('null' === $type1) { @@ -155,24 +207,6 @@ private function getValidTypes(string $content): string return 0; }); - return implode('|', $types); - } - - /** - * @param string $content - * - * @return array - */ - private function getTypes(string $content): array - { - $types = []; - while ('' !== $content && false !== $content) { - preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches); - - $types[] = $matches['type']; - $content = substr($content, strlen($matches['type']) + 1); - } - return $types; } diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index 1cde4a6..0791255 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -1,24 +1,5 @@ 1, + 8 => 1, + 9 => 1, + 10 => 1, + 11 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + 16 => 1, + 17 => 1, + 20 => 1, + 21 => 1, + 22 => 1, + 23 => 1, + 24 => 1, 26 => 1, 27 => 1, 28 => 1, - 29 => 1, - 30 => 1, + 31 => 1, 32 => 1, 33 => 1, - 34 => 1, - 35 => 1, 36 => 1, + 37 => 1, + 38 => 1, 39 => 1, 40 => 1, - 41 => 1, - 42 => 1, 43 => 1, + 44 => 1, 45 => 1, - 46 => 1, - 47 => 1, + 49 => 1, 50 => 1, 51 => 1, - 52 => 1, - 55 => 1, - 56 => 1, - 57 => 1, - 58 => 1, - 59 => 1, - 62 => 1, - 63 => 1, - 64 => 1, ]; } From 0e28ec0eea741e72d7d70215f52347abb4d6db1a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 29 Dec 2019 03:34:14 +0100 Subject: [PATCH 5/7] :bug: Fix and reorganize tests --- .../NamingConventions/ValidTypeHintSniff.php | 10 ++- .../ValidTypeHintUnitTest.inc | 79 ++++++++++++------- .../ValidTypeHintUnitTest.inc.fixed | 79 ++++++++++++------- .../ValidTypeHintUnitTest.php | 31 ++++---- 4 files changed, 120 insertions(+), 79 deletions(-) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index c92a37f..fec73ff 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -161,6 +161,8 @@ private function getValidTypes(string $content): string $validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']); } elseif (isset($matches['object']) && '' !== $matches['object']) { $validType = $this->getValidObjectType($matches['objectContent']); + } elseif (isset($matches['array']) && '' !== $matches['array']) { + $validType = $this->getValidTypes(substr($matches['array'], 0, -2)).'[]'; } else { $validType = $this->getValidType($matches['type']); } @@ -215,6 +217,8 @@ private function orderTypes(array $types): array * @param string $genericContent * * @return string + * + * @throws DeepExitException */ private function getValidGenericType(string $genericName, string $genericContent): string { @@ -234,6 +238,8 @@ private function getValidGenericType(string $genericName, string $genericContent * @param string $objectContent * * @return string + * + * @throws DeepExitException */ private function getValidObjectType(string $objectContent): string { @@ -263,10 +269,6 @@ private function getValidObjectType(string $objectContent): string */ private function getValidType(string $typeName): string { - if ('[]' === substr($typeName, -2)) { - return $this->getValidType(substr($typeName, 0, -2)).'[]'; - } - $lowerType = strtolower($typeName); switch ($lowerType) { case 'bool': diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index 0791255..01c8273 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -2,51 +2,72 @@ // Only PHPDoc comments are checked /* @var boolean $a */ +/** @var boolean $a */ /** + * Check simple type + * * @param boolean $a - * @param Boolean $a * @param integer $b - * @param double $c - * @param real $c - * - * @return boolean - * @return Boolean - * @return integer - * @return double - * @return real + * @param double $c + * @param real $c */ -/** @var boolean $a */ -/** @var Boolean $a */ -/** @var integer $b */ -/** @var double $c */ -/** @var real $c */ - -/** @method integer|string */ -/** @method array|integer[]|array truc */ -/** @method array|integer[]|array> truc */ +/** + * Don't care about case + * + * @return BoOlEAn $a + * @return boolean $a + */ /** - * @return null|int - * @return string[]|null|int[] - * @return Foo|null|int[] + * Works with different tags + * + * @method integer someFunction() + * @property integer $integer + * @property-read integer $integer + * @property-write integer $integer + * @throws integer */ -/** @method array < integer , null | boolean > | integer [ ] | array < array < integer > > truc */ -/** @method Generator truc */ -/** @method Generator, array, array, array> truc */ -/** @method array { integer: integer, boolean?: boolean } truc */ -/** @param class-string|class-string|integer truc */ +/** + * Union et intersection types + * + * @param integer|(string|boolean) + * @param integer&string|boolean This syntax is not valid + * @param (integer&string)|boolean + * @param (integer|((string|boolean))) + */ /** + * Array, generic and object types + * + * @param array|integer[]|array foo + * @param Generator bar + * @param Generator, array, array, array> baz + * @param array[]|boolean * @param array{0: integer, 1?: integer} * @param array{integer, integer} * @param array{foo: integer, bar: string} */ /** - * @param (integer|string)|bool - * @param (integer&string)|bool - * @param integer&string|bool + * Class-string + * + * @param class-string|class-string|integer + */ + +/** + * Handle space (Last one is a comment, correctly not replaced) + * + * @param array < integer , integer | boolean > | integer [ ] | array < array < integer > > integer + * @param array { integer: integer, boolean?: boolean } integer + */ + +/** + * null type should be the last one + * + * @param null|int + * @param string[]|null|int[] + * @param Foo|null|int[] */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed index 34cb598..ca8c972 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed @@ -2,51 +2,72 @@ // Only PHPDoc comments are checked /* @var boolean $a */ +/** @var bool $a */ /** - * @param bool $a + * Check simple type + * * @param bool $a * @param int $b - * @param float $c - * @param float $c - * - * @return bool - * @return bool - * @return int - * @return float - * @return float + * @param float $c + * @param float $c */ -/** @var bool $a */ -/** @var bool $a */ -/** @var int $b */ -/** @var float $c */ -/** @var float $c */ - -/** @method int|string */ -/** @method array|int[]|array truc */ -/** @method array|int[]|array> truc */ +/** + * Don't care about case + * + * @return bool $a + * @return bool $a + */ /** - * @return int|null - * @return string[]|int[]|null - * @return Foo|int[]|null + * Works with different tags + * + * @method int someFunction() + * @property int $integer + * @property-read int $integer + * @property-write int $integer + * @throws int */ -/** @method array|int[]|array> truc */ -/** @method Generator truc */ -/** @method Generator, array, array, array> truc */ -/** @method array{integer: int, boolean?: bool} truc */ -/** @param class-string|class-string|int truc */ +/** + * Union et intersection types + * + * @param int|(string|bool) + * @param integer&string|boolean This syntax is not valid + * @param (int&string)|bool + * @param (int|((string|bool))) + */ /** + * Array, generic and object types + * + * @param array|int[]|array foo + * @param Generator bar + * @param Generator, array, array, array> baz + * @param array[]|bool * @param array{0: int, 1?: int} * @param array{int, int} * @param array{foo: int, bar: string} */ /** - * @param (int|string)|bool - * @param (int&string)|bool - * @param integer&string|bool + * Class-string + * + * @param class-string|class-string|int + */ + +/** + * Handle space (Last one is a comment, correctly not replaced) + * + * @param array|int[]|array> integer + * @param array{integer: int, boolean?: bool} integer + */ + +/** + * null type should be the last one + * + * @param int|null + * @param string[]|int[]|null + * @param Foo|int[]|null */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php index f70e080..6a1003a 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php @@ -19,38 +19,35 @@ class ValidTypeHintUnitTest extends AbstractSniffUnitTest protected function getErrorList(): array { return [ - 7 => 1, - 8 => 1, - 9 => 1, + 5 => 1, 10 => 1, 11 => 1, + 12 => 1, 13 => 1, - 14 => 1, - 15 => 1, - 16 => 1, - 17 => 1, + 19 => 1, 20 => 1, - 21 => 1, - 22 => 1, - 23 => 1, - 24 => 1, 26 => 1, 27 => 1, 28 => 1, - 31 => 1, - 32 => 1, - 33 => 1, + 29 => 1, + 30 => 1, 36 => 1, 37 => 1, 38 => 1, 39 => 1, - 40 => 1, - 43 => 1, - 44 => 1, 45 => 1, + 46 => 1, + 47 => 1, + 48 => 1, 49 => 1, 50 => 1, 51 => 1, + 57 => 1, + 63 => 1, + 64 => 1, + 70 => 1, + 71 => 1, + 72 => 1, ]; } From 0bddaa8a63eb5871b32585cd58af1f1615756f1f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 1 Jan 2020 15:43:36 +0100 Subject: [PATCH 6/7] :sparkles: Case insensitive --- .../NamingConventions/ValidTypeHintSniff.php | 72 ++++++++++++++----- .../ValidTypeHintUnitTest.inc | 1 + .../ValidTypeHintUnitTest.inc.fixed | 1 + .../ValidTypeHintUnitTest.php | 5 +- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index fec73ff..908c742 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -7,7 +7,6 @@ use PHP_CodeSniffer\Exceptions\DeepExitException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Common; use SymfonyCustom\Helpers\SniffHelper; /** @@ -69,12 +68,18 @@ class ValidTypeHintSniff implements Sniff | (? class-string(?: - \s*<\s*[\\\\\w]+\s*> + \s*<\s* + (? + (?&simple) + ) + \s*> )? ) | (? - [@$?]?[\\\\\w]+ + \\\\?\w+(?:\\\\\w+)* + | + \$this ) ) ) @@ -84,6 +89,36 @@ class-string(?: ) '; + private const PRIMITIVES_TYPES = [ + 'string', + 'int', + 'float', + 'bool', + 'array', + 'resource', + 'null', + 'callable', + 'iterable', + ]; + private const KEYWORD_TYPES = [ + 'mixed', + 'void', + 'object', + 'number', + 'false', + 'true', + 'self', + 'static', + '$this', + ]; + private const ALIAS_TYPES = [ + 'boolean' => 'bool', + 'integer' => 'int', + 'double' => 'float', + 'real' => 'float', + 'callback' => 'callable', + ]; + /** * @return array */ @@ -105,7 +140,7 @@ public function process(File $phpcsFile, $stackPtr): void } $matchingResult = preg_match( - '{^'.self::REGEX_TYPES.'(?:[\s\t].*)?$}sx', + '{^'.self::REGEX_TYPES.'(?:[\s\t].*)?$}six', $tokens[$stackPtr + 2]['content'], $matches ); @@ -153,16 +188,18 @@ private function getValidTypes(string $content): string $types = []; $separators = []; while ('' !== $content && false !== $content) { - preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches); + preg_match('{^'.self::REGEX_TYPES.'$}ix', $content, $matches); - if (isset($matches['multiple']) && '' !== $matches['multiple']) { + if (isset($matches['array']) && '' !== $matches['array']) { + $validType = $this->getValidTypes(substr($matches['array'], 0, -2)).'[]'; + } elseif (isset($matches['multiple']) && '' !== $matches['multiple']) { $validType = '('.$this->getValidTypes($matches['mutipleContent']).')'; } elseif (isset($matches['generic']) && '' !== $matches['generic']) { $validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']); } elseif (isset($matches['object']) && '' !== $matches['object']) { $validType = $this->getValidObjectType($matches['objectContent']); - } elseif (isset($matches['array']) && '' !== $matches['array']) { - $validType = $this->getValidTypes(substr($matches['array'], 0, -2)).'[]'; + } elseif (isset($matches['classString']) && '' !== $matches['classString']) { + $validType = preg_replace('/class-string/i', 'class-string', $matches['classString']); } else { $validType = $this->getValidType($matches['type']); } @@ -225,7 +262,7 @@ private function getValidGenericType(string $genericName, string $genericContent $validType = $this->getValidType($genericName).'<'; while ('' !== $genericContent && false !== $genericContent) { - preg_match('{^'.self::REGEX_TYPES.',?}x', $genericContent, $matches); + preg_match('{^'.self::REGEX_TYPES.',?}ix', $genericContent, $matches); $validType .= $this->getValidTypes($matches['types']).', '; $genericContent = substr($genericContent, strlen($matches['types']) + 1); @@ -253,7 +290,7 @@ private function getValidObjectType(string $objectContent): string $objectContent = $split[2]; } - preg_match('{^'.self::REGEX_TYPES.',?}x', $objectContent, $matches); + preg_match('{^'.self::REGEX_TYPES.',?}ix', $objectContent, $matches); $validType .= $this->getValidTypes($matches['types']).', '; $objectContent = substr($objectContent, strlen($matches['types']) + 1); @@ -270,15 +307,14 @@ private function getValidObjectType(string $objectContent): string private function getValidType(string $typeName): string { $lowerType = strtolower($typeName); - switch ($lowerType) { - case 'bool': - case 'boolean': - return 'bool'; - case 'int': - case 'integer': - return 'int'; + if (in_array($lowerType, self::PRIMITIVES_TYPES) || in_array($lowerType, self::KEYWORD_TYPES)) { + return $lowerType; + } + + if (isset(self::ALIAS_TYPES[$lowerType])) { + return self::ALIAS_TYPES[$lowerType]; } - return Common::suggestType($typeName); + return $typeName; } } diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index 01c8273..e933258 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -55,6 +55,7 @@ * Class-string * * @param class-string|class-string|integer + * @param class-STRING|cLass-stRIng|int */ /** diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed index ca8c972..c807b44 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed @@ -55,6 +55,7 @@ * Class-string * * @param class-string|class-string|int + * @param class-string|class-string|int */ /** diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php index 6a1003a..a38f1be 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php @@ -43,11 +43,12 @@ protected function getErrorList(): array 50 => 1, 51 => 1, 57 => 1, - 63 => 1, + 58 => 1, 64 => 1, - 70 => 1, + 65 => 1, 71 => 1, 72 => 1, + 73 => 1, ]; } From 2c5817610a922e504f5ad84fa51a779838b080ae Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 2 Jan 2020 02:03:08 +0100 Subject: [PATCH 7/7] :package: Drop some supports --- .../NamingConventions/ValidTypeHintSniff.php | 68 ++++++++----------- .../ValidTypeHintUnitTest.inc | 12 +--- .../ValidTypeHintUnitTest.inc.fixed | 10 +-- .../ValidTypeHintUnitTest.php | 14 ++-- 4 files changed, 40 insertions(+), 64 deletions(-) diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index 908c742..7983f42 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -19,8 +19,7 @@ class ValidTypeHintSniff implements Sniff * is array of , eg `int[]` or `\Foo[]` * is generic collection type, like `array`, `Collection` or more complex` * is array key => value type, like `array{type: string, name: string, value: mixed}` - * is Foo::class type, like `class-string` or `class-string` - * is , , , or type + * is , , , type * is one or more types alternated via `|`, like `int|bool[]|Collection` */ private const REGEX_TYPES = ' @@ -66,16 +65,6 @@ class ValidTypeHintSniff implements Sniff \s*} ) | - (? - class-string(?: - \s*<\s* - (? - (?&simple) - ) - \s*> - )? - ) - | (? \\\\?\w+(?:\\\\\w+)* | @@ -89,28 +78,30 @@ class-string(?: ) '; - private const PRIMITIVES_TYPES = [ - 'string', - 'int', - 'float', - 'bool', - 'array', - 'resource', - 'null', - 'callable', - 'iterable', - ]; - private const KEYWORD_TYPES = [ - 'mixed', - 'void', - 'object', - 'number', - 'false', - 'true', - 'self', - 'static', - '$this', + /** + * False if the type is not a reserved keyword and the check can't be case insensitive + **/ + private const TYPES = [ + 'array' => true, + 'bool' => true, + 'callable' => true, + 'false' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'mixed' => false, + 'null' => true, + 'number' => false, + 'object' => true, + 'resource' => false, + 'self' => true, + 'static' => true, + 'string' => true, + 'true' => true, + 'void' => true, + '$this' => true, ]; + private const ALIAS_TYPES = [ 'boolean' => 'bool', 'integer' => 'int', @@ -198,8 +189,6 @@ private function getValidTypes(string $content): string $validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']); } elseif (isset($matches['object']) && '' !== $matches['object']) { $validType = $this->getValidObjectType($matches['objectContent']); - } elseif (isset($matches['classString']) && '' !== $matches['classString']) { - $validType = preg_replace('/class-string/i', 'class-string', $matches['classString']); } else { $validType = $this->getValidType($matches['type']); } @@ -307,12 +296,13 @@ private function getValidObjectType(string $objectContent): string private function getValidType(string $typeName): string { $lowerType = strtolower($typeName); - if (in_array($lowerType, self::PRIMITIVES_TYPES) || in_array($lowerType, self::KEYWORD_TYPES)) { - return $lowerType; + if (isset(self::TYPES[$lowerType])) { + return self::TYPES[$lowerType] ? $lowerType : $typeName; } - if (isset(self::ALIAS_TYPES[$lowerType])) { - return self::ALIAS_TYPES[$lowerType]; + // This can't be case insensitive since this is not reserved keyword + if (isset(self::ALIAS_TYPES[$typeName])) { + return self::ALIAS_TYPES[$typeName]; } return $typeName; diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc index e933258..33393be 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc @@ -14,10 +14,11 @@ */ /** - * Don't care about case + * Don't care about case for reserved keyword * - * @return BoOlEAn $a + * @return BOOL $a * @return boolean $a + * @return Boolean $a */ /** @@ -51,13 +52,6 @@ * @param array{foo: integer, bar: string} */ -/** - * Class-string - * - * @param class-string|class-string|integer - * @param class-STRING|cLass-stRIng|int - */ - /** * Handle space (Last one is a comment, correctly not replaced) * diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed index c807b44..23a2f38 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed @@ -14,10 +14,11 @@ */ /** - * Don't care about case + * Don't care about case for reserved keyword * * @return bool $a * @return bool $a + * @return Boolean $a */ /** @@ -51,13 +52,6 @@ * @param array{foo: int, bar: string} */ -/** - * Class-string - * - * @param class-string|class-string|int - * @param class-string|class-string|int - */ - /** * Handle space (Last one is a comment, correctly not replaced) * diff --git a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php index a38f1be..8063244 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php @@ -26,29 +26,27 @@ protected function getErrorList(): array 13 => 1, 19 => 1, 20 => 1, - 26 => 1, 27 => 1, 28 => 1, 29 => 1, 30 => 1, - 36 => 1, + 31 => 1, 37 => 1, 38 => 1, 39 => 1, - 45 => 1, + 40 => 1, 46 => 1, 47 => 1, 48 => 1, 49 => 1, 50 => 1, 51 => 1, - 57 => 1, + 52 => 1, 58 => 1, - 64 => 1, + 59 => 1, 65 => 1, - 71 => 1, - 72 => 1, - 73 => 1, + 66 => 1, + 67 => 1, ]; }