From ddbb4d52b5f36420c226c31184923dd3c70c220f Mon Sep 17 00:00:00 2001 From: romanzubach Date: Mon, 17 Nov 2025 13:57:55 +0100 Subject: [PATCH] Update squizlabs/php_codesniffer from 3.13.5 to 4.0.1 --- .github/workflows/test.yaml | 7 +- Swivl/Helpers/Common.php | 25 + Swivl/Helpers/TypeHelper.php | 9 - .../FullyQualifiedClassNameUsageSniff.php | 7 - .../FullyQualifiedClassNameUsageSniff.php | 7 - .../Commenting/FunctionCommentSniff.php | 574 +++++++++++++++++- .../Commenting/VariableCommentSniff.php | 8 - .../Formatting/BlankLineBeforeReturnSniff.php | 7 - .../Sniffs/Namespaces/UseDeclarationSniff.php | 14 +- .../InterfaceSuffixSniff.php | 7 - .../WhiteSpace/DiscourageFitzinatorSniff.php | 7 - .../FunctionClosingBraceSpaceSniff.php | 19 - Swivl/ruleset.xml | 10 +- Swivl/slevomat.xml | 5 +- composer.json | 6 +- 15 files changed, 609 insertions(+), 103 deletions(-) create mode 100644 Swivl/Helpers/Common.php diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6dad793..62fe64a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -27,15 +27,15 @@ jobs: - php-version: '8.2' dependencies: highest allowed-to-fail: true - variant: 'squizlabs/php_codesniffer:"dev-master"' + variant: 'squizlabs/php_codesniffer:"4.x-dev"' - php-version: '8.3' dependencies: highest allowed-to-fail: true - variant: 'squizlabs/php_codesniffer:"dev-master"' + variant: 'squizlabs/php_codesniffer:"4.x-dev"' - php-version: '8.4' dependencies: highest allowed-to-fail: true - variant: 'squizlabs/php_codesniffer:"dev-master"' + variant: 'squizlabs/php_codesniffer:"4.x-dev"' steps: - name: Checkout @@ -58,6 +58,7 @@ jobs: composer-options: "--prefer-dist --prefer-stable" - name: Validate composer + if: matrix.variant == 'normal' run: composer validate --strict --no-check-lock - name: Check Code Style diff --git a/Swivl/Helpers/Common.php b/Swivl/Helpers/Common.php new file mode 100644 index 0000000..fcd3b69 --- /dev/null +++ b/Swivl/Helpers/Common.php @@ -0,0 +1,25 @@ + true, ]; - private const SHORT_SCALAR_TYPES = ['int', 'bool']; - public static function isTypeTraversable(string $mixedType): bool { foreach (explode('|', self::normalizeType($mixedType)) as $type) { @@ -33,13 +31,6 @@ public static function isTypeTraversable(string $mixedType): bool return false; } - public static function allowShortScalarTypes(): void - { - if ($missedScalarTypes = array_diff(self::SHORT_SCALAR_TYPES, Common::$allowedTypes)) { - Common::$allowedTypes = array_merge(Common::$allowedTypes, $missedScalarTypes); - } - } - public static function normalizeType(string $mixedType): string { if ($mixedType !== '' && $mixedType[0] === '?') { diff --git a/Swivl/Sniffs/Classes/FullyQualifiedClassNameUsageSniff.php b/Swivl/Sniffs/Classes/FullyQualifiedClassNameUsageSniff.php index babaef7..0f8775b 100644 --- a/Swivl/Sniffs/Classes/FullyQualifiedClassNameUsageSniff.php +++ b/Swivl/Sniffs/Classes/FullyQualifiedClassNameUsageSniff.php @@ -28,13 +28,6 @@ class FullyQualifiedClassNameUsageSniff implements Sniff */ protected $checkedPos = []; - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP']; - /** * Returns an array of tokens this test wants to listen for. * diff --git a/Swivl/Sniffs/Commenting/FullyQualifiedClassNameUsageSniff.php b/Swivl/Sniffs/Commenting/FullyQualifiedClassNameUsageSniff.php index 3bc3fc8..0238123 100644 --- a/Swivl/Sniffs/Commenting/FullyQualifiedClassNameUsageSniff.php +++ b/Swivl/Sniffs/Commenting/FullyQualifiedClassNameUsageSniff.php @@ -14,13 +14,6 @@ */ class FullyQualifiedClassNameUsageSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP']; - /** * Returns an array of tokens this test wants to listen for. * diff --git a/Swivl/Sniffs/Commenting/FunctionCommentSniff.php b/Swivl/Sniffs/Commenting/FunctionCommentSniff.php index ac1bc83..f5dec44 100644 --- a/Swivl/Sniffs/Commenting/FunctionCommentSniff.php +++ b/Swivl/Sniffs/Commenting/FunctionCommentSniff.php @@ -2,12 +2,13 @@ namespace Swivl\Sniffs\Commenting; +use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Standards\Generic\Sniffs\Commenting\DocCommentSniff; use PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\FunctionCommentSniff as SquizFunctionCommentSniff; use PHP_CodeSniffer\Util\Tokens; +use Swivl\Helpers\Common; use Swivl\Helpers\FunctionHelper; -use Swivl\Helpers\TypeHelper; /** * FunctionCommentSniff @@ -28,10 +29,10 @@ class FunctionCommentSniff extends SquizFunctionCommentSniff */ private $docCommentSniff; - public function __construct() - { - TypeHelper::allowShortScalarTypes(); - } + /** + * @var integer|string|null + */ + private $phpVersion = null; /** * {@inheritDoc} @@ -53,7 +54,7 @@ public function process(File $phpcsFile, $stackPtr) } $tokens = $phpcsFile->getTokens(); - $ignore = Tokens::$methodPrefixes; + $ignore = Tokens::METHOD_MODIFIERS; $ignore[T_WHITESPACE] = T_WHITESPACE; for ($commentEnd = ($stackPtr - 1); $commentEnd >= 0; $commentEnd--) { @@ -118,18 +119,39 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$commentEnd]['line'] !== ($tokens[$stackPtr]['line'] - 1)) { for ($i = ($commentEnd + 1); $i < $stackPtr; $i++) { - if ($tokens[$i]['column'] !== 1) { + if (isset($tokens[$i]['attribute_closer']) === true) { + $i = $tokens[$i]['attribute_closer']; continue; } if ( - $tokens[$i]['code'] === T_WHITESPACE - && $tokens[$i]['line'] !== $tokens[($i + 1)]['line'] + $tokens[$i]['column'] !== 1 + || $tokens[$i]['code'] !== T_WHITESPACE + || $tokens[$i]['line'] === $tokens[($i + 1)]['line'] + || isset(Tokens::PHPCS_ANNOTATION_TOKENS[$tokens[($i - 1)]['code']]) === true ) { - $error = 'There must be no blank lines after the function comment'; - $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); - break; + continue; } + + $nextNonWhitespace = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); + $error = 'There must be no blank lines between the function comment and the declaration'; + $fix = $phpcsFile->addFixableError($error, $i, 'SpacingAfter'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + for ($j = $i; $j < $nextNonWhitespace; $j++) { + if ($tokens[$j]['line'] === $tokens[$nextNonWhitespace]['line']) { + break; + } + + $phpcsFile->fixer->replaceToken($j, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + + $i = $nextNonWhitespace; } } @@ -269,4 +291,532 @@ protected function processDocComment(File $phpcsFile, int $commentStart): void $this->docCommentSniff->process($phpcsFile, $commentStart); } + + /** + * {@inheritDoc} + * + * This method is the copy of the parent method, with the only difference that we use custom Common service + */ + protected function processReturn(File $phpcsFile, int $stackPtr, int $commentStart) + { + $tokens = $phpcsFile->getTokens(); + $return = null; + + if ($this->skipIfInheritdoc === true) { + if ($this->checkInheritdoc($phpcsFile, $stackPtr, $commentStart) === true) { + return; + } + } + + foreach ($tokens[$commentStart]['comment_tags'] as $tag) { + if ($tokens[$tag]['content'] === '@return') { + if ($return !== null) { + $error = 'Only 1 @return tag is allowed in a function comment'; + $phpcsFile->addError($error, $tag, 'DuplicateReturn'); + + return; + } + + $return = $tag; + } + } + + // Skip constructor and destructor. + $methodName = $phpcsFile->getDeclarationName($stackPtr); + $isSpecialMethod = in_array($methodName, $this->specialMethods, true); + + if ($return !== null) { + $content = $tokens[($return + 2)]['content']; + if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) { + $error = 'Return type missing for @return tag in function comment'; + $phpcsFile->addError($error, $return, 'MissingReturnType'); + } else { + // Support both a return type and a description. + preg_match('`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9_\[\]]+))*)( .*)?`i', $content, $returnParts); + if (isset($returnParts[1]) === false) { + return; + } + + $returnType = $returnParts[1]; + + // Check return type (can be multiple, separated by '|'). + $typeNames = explode('|', $returnType); + $suggestedNames = []; + foreach ($typeNames as $typeName) { + $suggestedName = Common::suggestType($typeName); + if (in_array($suggestedName, $suggestedNames, true) === false) { + $suggestedNames[] = $suggestedName; + } + } + + $suggestedType = implode('|', $suggestedNames); + if ($returnType !== $suggestedType) { + $error = 'Expected "%s" but found "%s" for function return type'; + $data = [ + $suggestedType, + $returnType, + ]; + $fix = $phpcsFile->addFixableError($error, $return, 'InvalidReturn', $data); + if ($fix === true) { + $replacement = $suggestedType; + if (empty($returnParts[2]) === false) { + $replacement .= $returnParts[2]; + } + + $phpcsFile->fixer->replaceToken(($return + 2), $replacement); + unset($replacement); + } + } + + // If the return type is void, make sure there is + // no return statement in the function. + if ($returnType === 'void') { + if (isset($tokens[$stackPtr]['scope_closer']) === true) { + $endToken = $tokens[$stackPtr]['scope_closer']; + for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) { + if ( + $tokens[$returnToken]['code'] === T_CLOSURE + || $tokens[$returnToken]['code'] === T_ANON_CLASS + ) { + $returnToken = $tokens[$returnToken]['scope_closer']; + + continue; + } + + if ( + $tokens[$returnToken]['code'] === T_RETURN + || $tokens[$returnToken]['code'] === T_YIELD + || $tokens[$returnToken]['code'] === T_YIELD_FROM + ) { + break; + } + } + + if ($returnToken !== $endToken) { + // If the function is not returning anything, just + // exiting, then there is no problem. + $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true); + if ($tokens[$semicolon]['code'] !== T_SEMICOLON) { + $error = 'Function return type is void, but function contains return statement'; + $phpcsFile->addError($error, $return, 'InvalidReturnVoid'); + } + } + } + } elseif ( + $returnType !== 'mixed' + && $returnType !== 'never' + && in_array('void', $typeNames, true) === false + ) { + // If return type is not void, never, or mixed, there needs to be a + // return statement somewhere in the function that returns something. + if (isset($tokens[$stackPtr]['scope_closer']) === true) { + $endToken = $tokens[$stackPtr]['scope_closer']; + for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) { + if ( + $tokens[$returnToken]['code'] === T_CLOSURE + || $tokens[$returnToken]['code'] === T_ANON_CLASS + ) { + $returnToken = $tokens[$returnToken]['scope_closer']; + + continue; + } + + if ( + $tokens[$returnToken]['code'] === T_RETURN + || $tokens[$returnToken]['code'] === T_YIELD + || $tokens[$returnToken]['code'] === T_YIELD_FROM + ) { + break; + } + } + + if ($returnToken === $endToken) { + $error = 'Function return type is not void, but function has no return statement'; + $phpcsFile->addError($error, $return, 'InvalidNoReturn'); + } else { + $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true); + if ($tokens[$semicolon]['code'] === T_SEMICOLON) { + $error = 'Function return type is not void, but function is returning void here'; + $phpcsFile->addError($error, $returnToken, 'InvalidReturnNotVoid'); + } + } + } + } + } + } else { + if ($isSpecialMethod === true) { + return; + } + + $error = 'Missing @return tag in function comment'; + $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); + } + } + + /** + * {@inheritDoc} + * + * This method is the copy of the parent method, with the only difference that we use custom Common service + */ + protected function processParams(File $phpcsFile, int $stackPtr, int $commentStart) + { + if ($this->phpVersion === null) { + $this->phpVersion = Config::getConfigData('php_version'); + if ($this->phpVersion === null) { + $this->phpVersion = PHP_VERSION_ID; + } + } + + $tokens = $phpcsFile->getTokens(); + + if ($this->skipIfInheritdoc === true) { + if ($this->checkInheritdoc($phpcsFile, $stackPtr, $commentStart) === true) { + return; + } + } + + $params = []; + $maxType = 0; + $maxVar = 0; + foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { + if ($tokens[$tag]['content'] !== '@param') { + continue; + } + + $type = ''; + $typeSpace = 0; + $var = ''; + $varSpace = 0; + $comment = ''; + $commentLines = []; + if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) { + $matches = []; + preg_match('/((?:(?![$.]|&(?=\$)).)*)(?:((?:\.\.\.)?(?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[($tag + 2)]['content'], $matches); + + if (empty($matches) === false) { + $typeLen = strlen($matches[1]); + $type = trim($matches[1]); + $typeSpace = ($typeLen - strlen($type)); + $typeLen = strlen($type); + if ($typeLen > $maxType) { + $maxType = $typeLen; + } + } + + if ($tokens[($tag + 2)]['content'][0] === '$') { + $error = 'Missing parameter type'; + $phpcsFile->addError($error, $tag, 'MissingParamType'); + } elseif (isset($matches[2]) === true) { + $var = $matches[2]; + $varLen = strlen($var); + if ($varLen > $maxVar) { + $maxVar = $varLen; + } + + if (isset($matches[4]) === true) { + $varSpace = strlen($matches[3]); + $comment = $matches[4]; + $commentLines[] = [ + 'comment' => $comment, + 'token' => ($tag + 2), + 'indent' => $varSpace, + ]; + + // Any strings until the next tag belong to this comment. + if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) { + $end = $tokens[$commentStart]['comment_tags'][($pos + 1)]; + } else { + $end = $tokens[$commentStart]['comment_closer']; + } + + for ($i = ($tag + 3); $i < $end; $i++) { + if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { + $indent = 0; + if ($tokens[($i - 1)]['code'] === T_DOC_COMMENT_WHITESPACE) { + $indent = $tokens[($i - 1)]['length']; + } + + $comment .= ' ' . $tokens[$i]['content']; + $commentLines[] = [ + 'comment' => $tokens[$i]['content'], + 'token' => $i, + 'indent' => $indent, + ]; + } + } + } else { + $error = 'Missing parameter comment'; + $phpcsFile->addError($error, $tag, 'MissingParamComment'); + $commentLines[] = ['comment' => '']; + } + } else { + $error = 'Missing parameter name'; + $phpcsFile->addError($error, $tag, 'MissingParamName'); + } + } else { + $error = 'Missing parameter type'; + $phpcsFile->addError($error, $tag, 'MissingParamType'); + } + + $params[] = [ + 'tag' => $tag, + 'type' => $type, + 'var' => $var, + 'comment' => $comment, + 'commentLines' => $commentLines, + 'type_space' => $typeSpace, + 'var_space' => $varSpace, + ]; + } + + $realParams = $phpcsFile->getMethodParameters($stackPtr); + $foundParams = []; + + // We want to use ... for all variable length arguments, so added + // this prefix to the variable name so comparisons are easier. + foreach ($realParams as $pos => $param) { + if ($param['variable_length'] === true) { + $realParams[$pos]['name'] = '...' . $realParams[$pos]['name']; + } + } + + foreach ($params as $pos => $param) { + // If the type is empty, the whole line is empty. + if ($param['type'] === '') { + continue; + } + + // Check the param type value. + $typeNames = explode('|', $param['type']); + $suggestedTypeNames = []; + + foreach ($typeNames as $typeName) { + if ($typeName === '') { + continue; + } + + // Strip nullable operator. + if ($typeName[0] === '?') { + $typeName = substr($typeName, 1); + } + + $suggestedName = Common::suggestType($typeName); + $suggestedTypeNames[] = $suggestedName; + + if (count($typeNames) > 1) { + continue; + } + + // Check type hint for array and custom type. + $suggestedTypeHint = ''; + if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') { + $suggestedTypeHint = 'array'; + } elseif (strpos($suggestedName, 'callable') !== false) { + $suggestedTypeHint = 'callable'; + } elseif (strpos($suggestedName, 'callback') !== false) { + $suggestedTypeHint = 'callable'; + } elseif (isset(Common::ALLOWED_TYPES[$suggestedName]) === false) { + $suggestedTypeHint = $suggestedName; + } + + if ($this->phpVersion >= 70000) { + if ($suggestedName === 'string') { + $suggestedTypeHint = 'string'; + } elseif ($suggestedName === 'int' || $suggestedName === 'integer') { + $suggestedTypeHint = 'int'; + } elseif ($suggestedName === 'float') { + $suggestedTypeHint = 'float'; + } elseif ($suggestedName === 'bool' || $suggestedName === 'boolean') { + $suggestedTypeHint = 'bool'; + } + } + + if ($this->phpVersion >= 70200) { + if ($suggestedName === 'object') { + $suggestedTypeHint = 'object'; + } + } + + if ($this->phpVersion >= 80000) { + if ($suggestedName === 'mixed') { + $suggestedTypeHint = 'mixed'; + } + } + + if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true && $param['var'] !== '') { + $typeHint = $realParams[$pos]['type_hint']; + + // Remove namespace prefixes when comparing. + $compareTypeHint = substr($suggestedTypeHint, (strlen($typeHint) * -1)); + + if ($typeHint === '') { + $error = 'Type hint "%s" missing for %s'; + $data = [ + $suggestedTypeHint, + $param['var'], + ]; + + $errorCode = 'TypeHintMissing'; + if ( + $suggestedTypeHint === 'string' + || $suggestedTypeHint === 'int' + || $suggestedTypeHint === 'float' + || $suggestedTypeHint === 'bool' + ) { + $errorCode = 'Scalar' . $errorCode; + } + + $phpcsFile->addError($error, $stackPtr, $errorCode, $data); + } elseif ($typeHint !== $compareTypeHint && $typeHint !== '?' . $compareTypeHint) { + $error = 'Expected type hint "%s"; found "%s" for %s'; + $data = [ + $suggestedTypeHint, + $typeHint, + $param['var'], + ]; + $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); + } + } elseif ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) { + $typeHint = $realParams[$pos]['type_hint']; + if ($typeHint !== '') { + $error = 'Unknown type hint "%s" found for %s'; + $data = [ + $typeHint, + $param['var'], + ]; + $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data); + } + } + } + + $suggestedType = implode('|', $suggestedTypeNames); + if ($param['type'] !== $suggestedType) { + $error = 'Expected "%s" but found "%s" for parameter type'; + $data = [ + $suggestedType, + $param['type'], + ]; + + $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $content = $suggestedType; + $content .= str_repeat(' ', $param['type_space']); + $content .= $param['var']; + $content .= str_repeat(' ', $param['var_space']); + if (isset($param['commentLines'][0]) === true) { + $content .= $param['commentLines'][0]['comment']; + } + + $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content); + + // Fix up the indent of additional comment lines. + foreach ($param['commentLines'] as $lineNum => $line) { + if ($lineNum === 0 || $param['commentLines'][$lineNum]['indent'] === 0) { + continue; + } + + $diff = (strlen($param['type']) - strlen($suggestedType)); + $newIndent = ($param['commentLines'][$lineNum]['indent'] - $diff); + $phpcsFile->fixer->replaceToken( + ($param['commentLines'][$lineNum]['token'] - 1), + str_repeat(' ', $newIndent) + ); + } + + $phpcsFile->fixer->endChangeset(); + } + } + + if ($param['var'] === '') { + continue; + } + + $foundParams[] = $param['var']; + + // Check number of spaces after the type. + $this->checkSpacingAfterParamType($phpcsFile, $param, $maxType); + + // Make sure the param name is correct. + if (isset($realParams[$pos]) === true) { + $realName = $realParams[$pos]['name']; + $paramVarName = $param['var']; + + if ($param['var'][0] === '&') { + // Even when passed by reference, the variable name in $realParams does not have + // a leading '&'. This sniff will accept both '&$var' and '$var' in these cases. + $paramVarName = substr($param['var'], 1); + + // This makes sure that the 'MissingParamTag' check won't throw a false positive. + $foundParams[(count($foundParams) - 1)] = $paramVarName; + + if ($realParams[$pos]['pass_by_reference'] !== true && $realName === $paramVarName) { + // Don't complain about this unless the param name is otherwise correct. + $error = 'Doc comment for parameter %s is prefixed with "&" but parameter is not passed by reference'; + $code = 'ParamNameUnexpectedAmpersandPrefix'; + $data = [$paramVarName]; + + // We're not offering an auto-fix here because we can't tell if the docblock + // is wrong, or the parameter should be passed by reference. + $phpcsFile->addError($error, $param['tag'], $code, $data); + } + } + + if ($realName !== $paramVarName) { + $code = 'ParamNameNoMatch'; + $data = [ + $paramVarName, + $realName, + ]; + + $error = 'Doc comment for parameter %s does not match '; + if (strtolower($paramVarName) === strtolower($realName)) { + $error .= 'case of '; + $code = 'ParamNameNoCaseMatch'; + } + + $error .= 'actual variable name %s'; + + $phpcsFile->addError($error, $param['tag'], $code, $data); + } + } elseif (substr($param['var'], -4) !== ',...') { + // We must have an extra parameter comment. + $error = 'Superfluous parameter comment'; + $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment'); + } + + if ($param['comment'] === '') { + continue; + } + + // Check number of spaces after the var name. + $this->checkSpacingAfterParamName($phpcsFile, $param, $maxVar); + + // Param comments must start with a capital letter and end with a full stop. + if (preg_match('/^(\p{Ll}|\P{L})/u', $param['comment']) === 1) { + $error = 'Parameter comment must start with a capital letter'; + $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital'); + } + + $lastChar = substr($param['comment'], -1); + if ($lastChar !== '.') { + $error = 'Parameter comment must end with a full stop'; + $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop'); + } + } + + $realNames = []; + foreach ($realParams as $realParam) { + $realNames[] = $realParam['name']; + } + + // Report missing comments. + $diff = array_diff($realNames, $foundParams); + foreach ($diff as $neededParam) { + $error = 'Doc comment for parameter "%s" missing'; + $data = [$neededParam]; + $phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data); + } + } } diff --git a/Swivl/Sniffs/Commenting/VariableCommentSniff.php b/Swivl/Sniffs/Commenting/VariableCommentSniff.php index 222455e..306439e 100644 --- a/Swivl/Sniffs/Commenting/VariableCommentSniff.php +++ b/Swivl/Sniffs/Commenting/VariableCommentSniff.php @@ -4,7 +4,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; -use Swivl\Helpers\TypeHelper; /** * VariableCommentSniff @@ -15,13 +14,6 @@ */ class VariableCommentSniff extends AbstractVariableSniff { - public function __construct() - { - parent::__construct(); - - TypeHelper::allowShortScalarTypes(); - } - /** * Called to process class member vars. * diff --git a/Swivl/Sniffs/Formatting/BlankLineBeforeReturnSniff.php b/Swivl/Sniffs/Formatting/BlankLineBeforeReturnSniff.php index 96dd082..a449299 100644 --- a/Swivl/Sniffs/Formatting/BlankLineBeforeReturnSniff.php +++ b/Swivl/Sniffs/Formatting/BlankLineBeforeReturnSniff.php @@ -16,13 +16,6 @@ */ class BlankLineBeforeReturnSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP', 'JS']; - /** * Returns an array of tokens this test wants to listen for. * diff --git a/Swivl/Sniffs/Namespaces/UseDeclarationSniff.php b/Swivl/Sniffs/Namespaces/UseDeclarationSniff.php index 431c836..67482fa 100644 --- a/Swivl/Sniffs/Namespaces/UseDeclarationSniff.php +++ b/Swivl/Sniffs/Namespaces/UseDeclarationSniff.php @@ -4,6 +4,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; /** * UseDeclarationSniff @@ -12,13 +13,6 @@ */ class UseDeclarationSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP']; - /** * Returns an array of tokens this test wants to listen for. * @@ -43,6 +37,8 @@ public function process(File $phpcsFile, $stackPtr): void $usePtr = $stackPtr; $lastUsePtr = null; + $nameTokens = Tokens::NAME_TOKENS + [T_NS_SEPARATOR => T_NS_SEPARATOR]; + while ($usePtr = $phpcsFile->findNext([T_USE, T_CLASS, T_INTERFACE, T_TRAIT], $usePtr + 1)) { $token = $tokens[$usePtr]; @@ -66,8 +62,8 @@ public function process(File $phpcsFile, $stackPtr): void $phpcsFile->fixer->endChangeset(); } - if ($nsStartPtr = $phpcsFile->findNext([T_NS_SEPARATOR, T_STRING], $usePtr + 1)) { - if ($nsEndPtr = $phpcsFile->findNext([T_NS_SEPARATOR, T_STRING], $nsStartPtr + 1, null, true)) { + if ($nsStartPtr = $phpcsFile->findNext($nameTokens, $usePtr + 1)) { + if ($nsEndPtr = $phpcsFile->findNext($nameTokens, $nsStartPtr + 1, null, true)) { $namespace = $phpcsFile->getTokensAsString($nsStartPtr, $nsEndPtr - $nsStartPtr); $uses[$tokenLevel][$nsStartPtr] = $namespace; diff --git a/Swivl/Sniffs/NamingConventions/InterfaceSuffixSniff.php b/Swivl/Sniffs/NamingConventions/InterfaceSuffixSniff.php index 61c1635..ebe2a3e 100644 --- a/Swivl/Sniffs/NamingConventions/InterfaceSuffixSniff.php +++ b/Swivl/Sniffs/NamingConventions/InterfaceSuffixSniff.php @@ -16,13 +16,6 @@ */ class InterfaceSuffixSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP']; - /** * Returns an array of tokens this test wants to listen for. * diff --git a/Swivl/Sniffs/WhiteSpace/DiscourageFitzinatorSniff.php b/Swivl/Sniffs/WhiteSpace/DiscourageFitzinatorSniff.php index 0be6c66..1e9f272 100644 --- a/Swivl/Sniffs/WhiteSpace/DiscourageFitzinatorSniff.php +++ b/Swivl/Sniffs/WhiteSpace/DiscourageFitzinatorSniff.php @@ -14,13 +14,6 @@ */ class DiscourageFitzinatorSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP', 'JS', 'CSS']; - /** * Returns an array of tokens this test wants to listen for. * diff --git a/Swivl/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php b/Swivl/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php index 322f18c..238bad0 100644 --- a/Swivl/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php +++ b/Swivl/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php @@ -16,13 +16,6 @@ */ class FunctionClosingBraceSpaceSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = ['PHP', 'JS']; - /** * Returns an array of tokens this test wants to listen for. * @@ -51,18 +44,6 @@ public function process(File $phpcsFile, $stackPtr): void $closeBrace = $tokens[$stackPtr]['scope_closer']; $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBrace - 1), null, true); - // Special case for empty JS functions - if ($phpcsFile->tokenizerType === 'JS' && $prevContent === $tokens[$stackPtr]['scope_opener']) { - // In this case, the opening and closing brace must be - // right next to each other. - if ($tokens[$stackPtr]['scope_closer'] !== ($tokens[$stackPtr]['scope_opener'] + 1)) { - $error = 'The opening and closing braces of empty functions must be directly next to each other; e.g., function () {}'; - $phpcsFile->addError($error, $closeBrace, 'SpacingBetween'); - } - - return; - } - $braceLine = $tokens[$closeBrace]['line']; $prevLine = $tokens[$prevContent]['line']; diff --git a/Swivl/ruleset.xml b/Swivl/ruleset.xml index a324a17..c4bfd4f 100644 --- a/Swivl/ruleset.xml +++ b/Swivl/ruleset.xml @@ -69,8 +69,6 @@ - - @@ -174,6 +172,7 @@ 0 + @@ -193,6 +192,12 @@ 0 + + 0 + + + 0 + @@ -271,7 +276,6 @@ - diff --git a/Swivl/slevomat.xml b/Swivl/slevomat.xml index 0caa8b7..8b04af8 100644 --- a/Swivl/slevomat.xml +++ b/Swivl/slevomat.xml @@ -295,10 +295,11 @@ - + - + + diff --git a/composer.json b/composer.json index 479f75f..25d94f9 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,8 @@ } ], "require": { - "php": ">=7.1", - "squizlabs/php_codesniffer": "^3.6" + "php": ">=7.2", + "squizlabs/php_codesniffer": "^4.0" }, "conflict": { "doctrine/orm": "<2.9" @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "2.x-dev" } } }