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"
}
}
}