diff --git a/SymfonyCustom/Sniffs/Arrays/ArrayDeclarationSniff.php b/SymfonyCustom/Sniffs/Arrays/ArrayDeclarationSniff.php index 70d5784..b96d798 100644 --- a/SymfonyCustom/Sniffs/Arrays/ArrayDeclarationSniff.php +++ b/SymfonyCustom/Sniffs/Arrays/ArrayDeclarationSniff.php @@ -36,16 +36,8 @@ public function process(File $phpcsFile, $stackPtr): void $tokens = $phpcsFile->getTokens(); if (T_ARRAY === $tokens[$stackPtr]['code']) { - $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no'); - // Array keyword should be lower case. if (strtolower($tokens[$stackPtr]['content']) !== $tokens[$stackPtr]['content']) { - if (strtoupper($tokens[$stackPtr]['content']) === $tokens[$stackPtr]['content']) { - $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'upper'); - } else { - $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'mixed'); - } - $fix = $phpcsFile->addFixableError( 'Array keyword should be lower case; expected "array" but found "%s"', $stackPtr, @@ -56,8 +48,6 @@ public function process(File $phpcsFile, $stackPtr): void if ($fix) { $phpcsFile->fixer->replaceToken($stackPtr, 'array'); } - } else { - $phpcsFile->recordMetric($stackPtr, 'Array keyword case', 'lower'); } $arrayStart = $tokens[$stackPtr]['parenthesis_opener']; @@ -79,7 +69,6 @@ public function process(File $phpcsFile, $stackPtr): void } } } else { - $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes'); $arrayStart = $stackPtr; $arrayEnd = $tokens[$stackPtr]['bracket_closer']; } @@ -449,17 +438,15 @@ public function processMultiLineArray(File $phpcsFile, int $stackPtr, int $start ); if (T_COMMA !== $tokens[$trailingContent]['code']) { - $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'no'); $fix = $phpcsFile->addFixableError( 'Comma required after last value in array declaration', $trailingContent, 'NoCommaAfterLast' ); + if ($fix) { $phpcsFile->fixer->addContent($trailingContent, ','); } - } else { - $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'yes'); } $lastValueLine = $stackPtr; diff --git a/SymfonyCustom/Sniffs/Formatting/ConditionalReturnOrThrowSniff.php b/SymfonyCustom/Sniffs/Formatting/ConditionalReturnOrThrowSniff.php index 52e13ef..7dd2618 100644 --- a/SymfonyCustom/Sniffs/Formatting/ConditionalReturnOrThrowSniff.php +++ b/SymfonyCustom/Sniffs/Formatting/ConditionalReturnOrThrowSniff.php @@ -29,9 +29,10 @@ public function process(File $phpcsFile, $stackPtr): void $tokens = $phpcsFile->getTokens(); $opener = $phpcsFile->findPrevious([T_IF, T_CASE], $stackPtr); - if ($opener + if (false !== $opener && isset($tokens[$opener]['scope_closer']) - && $stackPtr <= $tokens[$opener]['scope_closer']) { + && $stackPtr <= $tokens[$opener]['scope_closer'] + ) { $isClosure = $phpcsFile->findPrevious(T_CLOSURE, $stackPtr, $opener); if (false !== $isClosure) { return; diff --git a/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php b/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php index dc5c920..50536ad 100644 --- a/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php +++ b/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php @@ -311,7 +311,7 @@ private function isClassUsed(File $phpcsFile, int $usePtr, int $classPtr): bool || ('class' === $type && ((T_DOC_COMMENT_STRING === $tokens[$classUsed]['code'] && preg_match( - '/(\s|\||\(|\<|\,|^)'.preg_quote($searchName, '/').'(\s|\||\\\\|\<|\,|\>|$|\[\])/i', + '/(\s|\||\(|\<|\,|\:|^)'.preg_quote($searchName, '/').'(\s|\||\\\\|\<|\,|\>|$|\[\])/i', $tokens[$classUsed]['content'] )) || (T_DOC_COMMENT_TAG === $tokens[$classUsed]['code'] diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php index 02386d8..2603444 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php @@ -39,13 +39,7 @@ public function process(File $phpcsFile, $stackPtr): void if (T_INTERFACE === $tokens[$stackPtr]['code']) { $name = $phpcsFile->findNext(T_STRING, $stackPtr); - if ($name && substr($tokens[$name]['content'], -9) !== 'Interface') { - $phpcsFile->addError( - 'Interface name is not suffixed with "Interface"', - $stackPtr, - 'InvalidInterfaceName' - ); - } + $this->checkSuffix($phpcsFile, $stackPtr, $name, 'Interface'); break; } @@ -53,13 +47,7 @@ public function process(File $phpcsFile, $stackPtr): void if (T_TRAIT === $tokens[$stackPtr]['code']) { $name = $phpcsFile->findNext(T_STRING, $stackPtr); - if ($name && substr($tokens[$name]['content'], -5) !== 'Trait') { - $phpcsFile->addError( - 'Trait name is not suffixed with "Trait"', - $stackPtr, - 'InvalidTraitName' - ); - } + $this->checkSuffix($phpcsFile, $stackPtr, $name, 'Trait'); break; } @@ -67,39 +55,22 @@ public function process(File $phpcsFile, $stackPtr): void if (T_EXTENDS === $tokens[$stackPtr]['code']) { $extend = $phpcsFile->findNext(T_STRING, $stackPtr); - if ($extend - && substr($tokens[$extend]['content'], -9) === 'Exception' - ) { + if ($extend && substr($tokens[$extend]['content'], -9) === 'Exception') { $class = $phpcsFile->findPrevious(T_CLASS, $stackPtr); $name = $phpcsFile->findNext(T_STRING, $class); - if ($name - && substr($tokens[$name]['content'], -9) !== 'Exception' - ) { - $phpcsFile->addError( - 'Exception name is not suffixed with "Exception"', - $stackPtr, - 'InvalidExceptionName' - ); - } + $this->checkSuffix($phpcsFile, $stackPtr, $name, 'Exception'); } break; } // Prefix abstract classes with Abstract. if (T_ABSTRACT === $tokens[$stackPtr]['code']) { - $name = $phpcsFile->findNext(T_STRING, $stackPtr); - $function = $phpcsFile->findNext(T_FUNCTION, $stackPtr); + $name = $phpcsFile->findNext([T_STRING, T_FUNCTION], $stackPtr); // Making sure we're not dealing with an abstract function - if ($name && (false === $function || $name < $function) - && substr($tokens[$name]['content'], 0, 8) !== 'Abstract' - ) { - $phpcsFile->addError( - 'Abstract class name is not prefixed with "Abstract"', - $stackPtr, - 'InvalidAbstractName' - ); + if (false !== $name && T_FUNCTION !== $tokens[$name]['code']) { + $this->checkPrefix($phpcsFile, $stackPtr, $name, 'Abstract'); } break; } @@ -107,4 +78,42 @@ public function process(File $phpcsFile, $stackPtr): void $stackPtr++; } } + + /** + * @param File $phpcsFile + * @param int $stackPtr + * @param int|bool $name + * @param string $prefix + */ + private function checkPrefix(File $phpcsFile, int $stackPtr, $name, string $prefix): void + { + $tokens = $phpcsFile->getTokens(); + + if (false !== $name && substr($tokens[$name]['content'], 0, strlen($prefix)) !== $prefix) { + $phpcsFile->addError( + "$prefix name is not prefixed with '$prefix'", + $stackPtr, + "Invalid{$prefix}Name" + ); + } + } + + /** + * @param File $phpcsFile + * @param int $stackPtr + * @param int|bool $name + * @param string $suffix + */ + private function checkSuffix(File $phpcsFile, int $stackPtr, $name, string $suffix): void + { + $tokens = $phpcsFile->getTokens(); + + if (false !== $name && substr($tokens[$name]['content'], -strlen($suffix)) !== $suffix) { + $phpcsFile->addError( + "$suffix name is not suffixed with '$suffix'", + $stackPtr, + "Invalid{$suffix}Name" + ); + } + } } diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php index 557f11e..df360de 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php @@ -48,9 +48,6 @@ public function process(File $phpcsFile, $stackPtr): void 'Invalid', [$filename] ); - $phpcsFile->recordMetric($stackPtr, 'Alphanumeric filename', 'no'); - } else { - $phpcsFile->recordMetric($stackPtr, 'Alphanumeric filename', 'yes'); } } } diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidScalarTypeNameSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidScalarTypeNameSniff.php index a0cf850..c236ebd 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidScalarTypeNameSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidScalarTypeNameSniff.php @@ -6,34 +6,25 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Util\Common; +use SymfonyCustom\Helpers\SniffHelper; /** * Throws errors if scalar type name are not valid. */ class ValidScalarTypeNameSniff implements Sniff { - /** - * Types to replace: key is type to replace, value is type to replace with. - * - * @var array - */ - public $types = [ - 'boolean' => 'bool', - 'double' => 'float', - 'integer' => 'int', - 'real' => 'float', - ]; + private const TYPING = '\\\\a-z0-9'; + private const OPENER = '\<\[\{\('; + private const CLOSER = '\>\]\}\)'; + private const MIDDLE = '\,\:\&\|'; /** * @return array */ public function register(): array { - $tokens = Tokens::$castTokens; - $tokens[] = T_DOC_COMMENT_OPEN_TAG; - - return $tokens; + return [T_DOC_COMMENT_TAG]; } /** @@ -43,91 +34,90 @@ public function register(): array public function process(File $phpcsFile, $stackPtr): void { $tokens = $phpcsFile->getTokens(); - if (T_DOC_COMMENT_OPEN_TAG === $tokens[$stackPtr]['code']) { - $this->validateDocComment($phpcsFile, $stackPtr); - } else { - $this->validateCast($phpcsFile, $stackPtr); - } - } - /** - * @param File $phpcsFile - * @param int $stackPtr - */ - private function validateDocComment(File $phpcsFile, int $stackPtr): void - { - $tokens = $phpcsFile->getTokens(); - foreach ($tokens[$stackPtr]['comment_tags'] as $commentTag) { - if (in_array( - $tokens[$commentTag]['content'], - ['@param', '@return', '@var'] - ) - ) { - $docString = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $commentTag); - if (false !== $docString) { - $stringParts = explode(' ', $tokens[$docString]['content']); - $typeName = $stringParts[0]; - $this->validateTypeName($phpcsFile, $docString, $typeName); + if (in_array($tokens[$stackPtr]['content'], SniffHelper::TAGS_WITH_TYPE)) { + preg_match( + '`^((?:' + .'['.self::OPENER.self::MIDDLE.']\s*' + .'|(?:['.self::TYPING.self::CLOSER.']\s+)(?=[\|'.self::OPENER.self::MIDDLE.self::CLOSER.'])' + .'|['.self::TYPING.self::CLOSER.']' + .')+)(.*)?`i', + $tokens[($stackPtr + 2)]['content'], + $match + ); + + if (isset($match[1]) === false) { + return; + } + + // Check type (can be multiple, separated by '|'). + $type = $match[1]; + $suggestedType = $this->getValidTypeName($type); + if ($type !== $suggestedType) { + $fix = $phpcsFile->addFixableError( + 'For type-hinting in PHPDocs, use %s instead of %s', + $stackPtr + 2, + 'Invalid', + [$suggestedType, $type] + ); + + if ($fix) { + $replacement = $suggestedType; + if (isset($match[2])) { + $replacement .= $match[2]; + } + + $phpcsFile->fixer->replaceToken($stackPtr + 2, $replacement); } } } } /** - * @param File $phpcsFile - * @param int $stackPtr + * @param string $typeName + * + * @return string */ - private function validateCast(File $phpcsFile, int $stackPtr): void + private function getValidTypeName(string $typeName): string { - $tokens = $phpcsFile->getTokens(); - preg_match('/^\(\s*(\S+)\s*\)$/', $tokens[$stackPtr]['content'], $matches); - $typeName = $matches[1]; + $typeNameWithoutSpace = str_replace(' ', '', $typeName); + $parts = preg_split( + '/([\|'.self::OPENER.self::MIDDLE.self::CLOSER.'])/', + $typeNameWithoutSpace, + -1, + PREG_SPLIT_DELIM_CAPTURE + ); + $partsNumber = count($parts) - 1; - $this->validateTypeName($phpcsFile, $stackPtr, $typeName); - } + $validType = ''; + for ($i = 0; $i < $partsNumber; $i += 2) { + $validType .= $this->suggestType($parts[$i]).$parts[$i + 1]; + } - /** - * @param File $phpcsFile - * @param int $stackPtr - * @param string $typeName - */ - private function validateTypeName(File $phpcsFile, int $stackPtr, string $typeName): void - { - $validTypeName = $this->getValidTypeName($typeName); - - if (null !== $validTypeName) { - $needFix = $phpcsFile->addFixableError( - 'For type-hinting in PHPDocs and casting, use %s instead of %s', - $stackPtr, - '', - [$validTypeName, $typeName] - ); - if ($needFix) { - $tokens = $phpcsFile->getTokens(); - $phpcsFile->fixer->beginChangeset(); - $newContent = str_replace( - $typeName, - $validTypeName, - $tokens[$stackPtr]['content'] - ); - $phpcsFile->fixer->replaceToken($stackPtr, $newContent); - $phpcsFile->fixer->endChangeset(); - } + if ('' !== $parts[$partsNumber]) { + $validType .= $this->suggestType($parts[$partsNumber]); } + + return $validType; } /** * @param string $typeName * - * @return string|null + * @return string */ - private function getValidTypeName(string $typeName): ?string + private function suggestType(string $typeName): string { - $typeName = strtolower($typeName); - if (isset($this->types[$typeName])) { - return $this->types[$typeName]; + $lowerType = strtolower($typeName); + switch ($lowerType) { + case 'bool': + case 'boolean': + return 'bool'; + case 'int': + case 'integer': + return 'int'; } - return null; + return Common::suggestType($typeName); } } diff --git a/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc b/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc index 2d7b1fa..d9c3149 100644 --- a/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc +++ b/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc @@ -136,10 +136,17 @@ namespace BugCollection; use Doctrine\Collection; use Doctrine\Value; +use Doctrine\Value2; use Doctrine\Key; +use Doctrine\Key2; +use Used\Schemetype; +use Used\Host; +use NotUsed\Path; /** * @param Collection $a + * @param Collection $a + * @param array{scheme:Schemetype,host: Host,path:string} $parsed_url * * @return null */ diff --git a/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc.fixed b/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc.fixed index e77acb7..4a9679f 100644 --- a/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.inc.fixed @@ -123,10 +123,16 @@ namespace BugCollection; use Doctrine\Collection; use Doctrine\Value; +use Doctrine\Value2; use Doctrine\Key; +use Doctrine\Key2; +use Used\Schemetype; +use Used\Host; /** * @param Collection $a + * @param Collection $a + * @param array{scheme:Schemetype,host: Host,path:string} $parsed_url * * @return null */ diff --git a/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.php b/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.php index 2d70b8f..834e4db 100644 --- a/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.php +++ b/SymfonyCustom/Tests/Namespaces/UnusedUseUnitTest.php @@ -37,6 +37,7 @@ protected function getErrorList(): array 127 => 1, 128 => 1, 129 => 1, + 144 => 1, ]; } diff --git a/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc b/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc index 6f95fff..3aeeac3 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc +++ b/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc @@ -22,14 +22,6 @@ echo ( float ) $a; // Only PHPDoc comments are checked /* @var boolean $a */ - -echo ( integer ) $a; -echo (boolean) $a; -echo (Boolean) $a; -echo (integer) $a; -echo (double) $a; -echo (real) $a; - /** * @param boolean $a * @param Boolean $a @@ -49,3 +41,7 @@ echo (real) $a; /** @var integer $b */ /** @var double $c */ /** @var real $c */ + +/** @method integer|string */ +/** @method array|integer[]|array truc */ +/** @method array| integer[] |string|(integer|boolean)[] truc */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc.fixed b/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc.fixed index 5a048a1..abe84d1 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc.fixed +++ b/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.inc.fixed @@ -22,14 +22,6 @@ echo ( float ) $a; // Only PHPDoc comments are checked /* @var boolean $a */ - -echo ( int ) $a; -echo (bool) $a; -echo (bool) $a; -echo (int) $a; -echo (float) $a; -echo (float) $a; - /** * @param bool $a * @param bool $a @@ -49,3 +41,7 @@ echo (float) $a; /** @var int $b */ /** @var float $c */ /** @var float $c */ + +/** @method int|string */ +/** @method array|int[]|array truc */ +/** @method array|int[]|string|(int|bool)[] truc */ diff --git a/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.php b/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.php index d929624..cb57f5d 100644 --- a/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.php +++ b/SymfonyCustom/Tests/NamingConventions/ValidScalarTypeNameUnitTest.php @@ -24,22 +24,19 @@ protected function getErrorList(): array 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, - 48 => 1, - 49 => 1, - 50 => 1, - 51 => 1, ]; }