diff --git a/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php b/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php index 7b7de4a1..d089e296 100644 --- a/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php +++ b/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php @@ -15,8 +15,9 @@ namespace CakePHP\Sniffs\Commenting; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff as PearFunctionCommentSniff; +use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Util\Common; +use PHP_CodeSniffer\Util\Tokens; /** * Parses and verifies the doc comments for functions. @@ -47,8 +48,139 @@ * @version Release: @package_version@ * @link http://pear.php.net/package/PHP_CodeSniffer */ -class FunctionCommentSniff extends PearFunctionCommentSniff +class FunctionCommentSniff implements Sniff { + /** + * Disable the check for functions with a lower visibility than the value given. + * + * Allowed values are public, protected, and private. + * + * @var string + */ + public $minimumVisibility = 'private'; + + /** + * Array of methods which do not require a return type. + * + * @var array + */ + public $specialMethods = [ + '__construct', + '__destruct', + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return [T_FUNCTION]; + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $scopeModifier = $phpcsFile->getMethodProperties($stackPtr)['scope']; + if ( + $scopeModifier === 'protected' + && $this->minimumVisibility === 'public' + || $scopeModifier === 'private' + && ($this->minimumVisibility === 'public' || $this->minimumVisibility === 'protected') + ) { + return; + } + + $tokens = $phpcsFile->getTokens(); + $ignore = Tokens::$methodPrefixes; + $ignore[] = T_WHITESPACE; + + $commentEnd = $phpcsFile->findPrevious($ignore, $stackPtr - 1, null, true); + if ($tokens[$commentEnd]['code'] === T_COMMENT) { + // Inline comments might just be closing comments for + // control structures or functions instead of function comments + // using the wrong comment type. If there is other code on the line, + // assume they relate to that code. + $prev = $phpcsFile->findPrevious($ignore, $commentEnd - 1, null, true); + if ($prev !== false && $tokens[$prev]['line'] === $tokens[$commentEnd]['line']) { + $commentEnd = $prev; + } + } + + if ( + $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG + && $tokens[$commentEnd]['code'] !== T_COMMENT + ) { + $previous = $commentEnd; + if ( + $tokens[$commentEnd]['type'] === 'T_ATTRIBUTE_END' + || $tokens[$commentEnd]['type'] === 'T_ATTRIBUTE' + ) { + while ($tokens[$previous]['type'] !== 'T_ATTRIBUTE') { + $previous--; + } + $previous--; + + $commentEnd = $phpcsFile->findPrevious($ignore, $previous, null, true); + if ($tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { + if ($tokens[$commentEnd]['line'] !== $tokens[$previous]['line'] - 1) { + $error = 'There must be no blank lines after the function comment'; + $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); + } + + return; + } + } + + $function = $phpcsFile->getDeclarationName($stackPtr); + $phpcsFile->addError( + 'Missing doc comment for function %s()', + $stackPtr, + 'Missing', + [$function] + ); + $phpcsFile->recordMetric($stackPtr, 'Function has doc comment', 'no'); + + return; + } else { + $phpcsFile->recordMetric($stackPtr, 'Function has doc comment', 'yes'); + } + + if ($tokens[$commentEnd]['code'] === T_COMMENT) { + $phpcsFile->addError('You must use "/**" style comments for a function comment', $stackPtr, 'WrongStyle'); + + return; + } + + if ($tokens[$commentEnd]['line'] !== $tokens[$stackPtr]['line'] - 1) { + $error = 'There must be no blank lines after the function comment'; + $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); + } + + $commentStart = $tokens[$commentEnd]['comment_opener']; + foreach ($tokens[$commentStart]['comment_tags'] as $tag) { + if ($tokens[$tag]['content'] === '@see') { + // Make sure the tag isn't empty. + $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); + if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { + $error = 'Content missing for @see tag in function comment'; + $phpcsFile->addError($error, $tag, 'EmptySees'); + } + } + } + + $this->processReturn($phpcsFile, $stackPtr, $commentStart); + $this->processThrows($phpcsFile, $stackPtr, $commentStart); + $this->processParams($phpcsFile, $stackPtr, $commentStart); + } + /** * Checks if the doc comment is an inheritDoc comment. * diff --git a/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php b/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php index 069b3659..9fbba40a 100644 --- a/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php +++ b/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php @@ -134,6 +134,11 @@ protected function assertNewLineAtTheBeginning(File $phpCsFile, $stackPointer) } $prevContentIndex = $phpCsFile->findPrevious(T_WHITESPACE, $firstTokenInLineIndex - 1, null, true); + + if ($tokens[$prevContentIndex]['type'] === 'T_ATTRIBUTE_END') { + return; + } + if ($tokens[$prevContentIndex]['type'] === 'T_DOC_COMMENT_CLOSE_TAG') { $firstTokenInLineIndex = $tokens[$prevContentIndex]['comment_opener']; while ($tokens[$firstTokenInLineIndex - 1]['line'] === $line) { diff --git a/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc b/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc index b92c6643..df8fc331 100644 --- a/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc +++ b/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc @@ -227,4 +227,16 @@ class Foo public function withInheritDocIncompleteTags($param) { } + + /** + * Some sentence. + * + * @param integer $param Some Param. + * @param boolean $otherParam Some Other Param. + * @return string Something. + */ + #[ReturnTypeWillChange] + public function returnWillChange($param, $otherParam) + { + } } diff --git a/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc.fixed b/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc.fixed index cd184290..bc2240a0 100644 --- a/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc.fixed +++ b/CakePHP/Tests/Commenting/FunctionCommentUnitTest.inc.fixed @@ -227,4 +227,16 @@ class Foo public function withInheritDocIncompleteTags($param) { } + + /** + * Some sentence. + * + * @param integer $param Some Param. + * @param boolean $otherParam Some Other Param. + * @return string Something. + */ + #[ReturnTypeWillChange] + public function returnWillChange($param, $otherParam) + { + } } diff --git a/composer.json b/composer.json index df32b682..2fd121be 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require": { "php": ">=7.2.0", "slevomat/coding-standard": "^6.3.6 || ^7.0", - "squizlabs/php_codesniffer": "^3.5.5" + "squizlabs/php_codesniffer": "^3.6" }, "require-dev": { "phpunit/phpunit": "^7.1"