diff --git a/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php b/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php index 28d3c9a5e1..4b0827cb60 100644 --- a/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php +++ b/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php @@ -61,9 +61,31 @@ public function process(File $phpcsFile, $stackPtr) if ($short === false) { // No content at all. $error = 'Doc comment is empty'; - $phpcsFile->addError($error, $stackPtr, 'Empty'); + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($i = $commentStart; $i <= $commentEnd; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + if (isset($tokens[($commentStart - 1)]) === true && isset($tokens[($commentEnd + 1)]) === true) { + $tokenBefore = $tokens[($commentStart - 1)]; + $tokenAfter = $tokens[($commentEnd + 1)]; + if ($tokenBefore['code'] === T_WHITESPACE + && $tokenBefore['content'] === $phpcsFile->eolChar + && $tokenAfter['code'] === T_WHITESPACE + && $tokenAfter['content'] === $phpcsFile->eolChar + ) { + $phpcsFile->fixer->replaceToken(($commentStart - 1), ''); + } + } + + $phpcsFile->fixer->endChangeset(); + } + return; - } + }//end if // The first line of the comment should just be the /** code. if ($tokens[$short]['line'] === $tokens[$stackPtr]['line']) { @@ -147,7 +169,21 @@ public function process(File $phpcsFile, $stackPtr) if (preg_match('/^\p{Ll}/u', $shortContent) === 1) { $error = 'Doc comment short description must start with a capital letter'; - $phpcsFile->addError($error, $short, 'ShortNotCapital'); + + $firstCharacter = mb_substr($shortContent, 0, 1); + $firstCharacterUpper = mb_strtoupper($firstCharacter); + + if ($firstCharacter === $firstCharacterUpper) { + $phpcsFile->addError($error, $short, 'ShortNotCapital'); + } else { + $fix = $phpcsFile->addFixableError($error, $short, 'ShortNotCapital'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($short, $firstCharacterUpper.mb_substr($tokens[$short]['content'], 1)); + $phpcsFile->fixer->endChangeset(); + } + } } $long = $phpcsFile->findNext($empty, ($shortEnd + 1), ($commentEnd - 1), true); @@ -173,7 +209,21 @@ public function process(File $phpcsFile, $stackPtr) if (preg_match('/^\p{Ll}/u', $tokens[$long]['content']) === 1) { $error = 'Doc comment long description must start with a capital letter'; - $phpcsFile->addError($error, $long, 'LongNotCapital'); + + $firstCharacter = mb_substr($tokens[$long]['content'][0], 0, 1); + $firstCharacterUpper = mb_strtoupper($firstCharacter); + + if ($firstCharacter === $firstCharacterUpper) { + $phpcsFile->addError($error, $long, 'LongNotCapital'); + } else { + $fix = $phpcsFile->addFixableError($error, $long, 'LongNotCapital'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($long, $firstCharacterUpper.mb_substr($tokens[$long]['content'], 1)); + $phpcsFile->fixer->endChangeset(); + } + } } }//end if }//end if @@ -183,6 +233,8 @@ public function process(File $phpcsFile, $stackPtr) return; } + $indent = str_repeat(' ', $tokens[$stackPtr]['column']); + $firstTag = $tokens[$commentStart]['comment_tags'][0]; $prev = $phpcsFile->findPrevious($empty, ($firstTag - 1), $stackPtr, true); if ($tokens[$firstTag]['line'] !== ($tokens[$prev]['line'] + 2) @@ -200,7 +252,6 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->fixer->replaceToken($i, ''); } - $indent = str_repeat(' ', $tokens[$stackPtr]['column']); $phpcsFile->fixer->addContent($prev, $phpcsFile->eolChar.$indent.'*'.$phpcsFile->eolChar); $phpcsFile->fixer->endChangeset(); } @@ -212,6 +263,7 @@ public function process(File $phpcsFile, $stackPtr) $tagGroups = []; $groupid = 0; $paramGroupid = null; + $firstTagAfterParams = null; foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { if ($pos > 0) { $prev = $phpcsFile->findPrevious( @@ -234,28 +286,93 @@ public function process(File $phpcsFile, $stackPtr) && $paramGroupid !== $groupid ) { $error = 'Parameter tags must be grouped together in a doc comment'; - $phpcsFile->addError($error, $tag, 'ParamGroup'); - } + $fix = $phpcsFile->addFixableError($error, $tag, 'ParamGroup'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $thisTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $tag, $commentStart, false, $phpcsFile->eolChar)); + + if (isset($tokens[$stackPtr]['comment_tags'][($pos + 1)]) === true) { + $nextTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($tag + 1), $commentEnd); + $nextTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $nextTag, $commentStart, false, $phpcsFile->eolChar)); + } else { + $nextTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $commentEnd, $commentStart, false, $phpcsFile->eolChar)); + } + + $thisTagContent = ''; + for ($i = $thisTagLineStart; $i < $nextTagLineStart; $i++) { + if ($tokens[($i - 1)]['content'] === $phpcsFile->eolChar + && $tokens[$i]['content'] === $indent + && $tokens[($i + 1)]['content'] === '*' + && $tokens[($i + 2)]['content'] === $phpcsFile->eolChar + ) { + break; + } + + $thisTagContent .= $tokens[$i]['content']; + $phpcsFile->fixer->replaceToken($i, ''); + } + + $insertionPointer = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, ($tag - 1), $commentStart, false, '@param'); + + while ($tokens[($insertionPointer - 1)]['content'] !== $phpcsFile->eolChar + || $tokens[$insertionPointer]['content'] !== $indent + || $tokens[($insertionPointer + 1)]['content'] !== '*' + || $tokens[($insertionPointer + 2)]['content'] !== $phpcsFile->eolChar + ) { + $insertionPointer++; + } + + $phpcsFile->fixer->addContentBefore($insertionPointer, $thisTagContent); + + $phpcsFile->fixer->endChangeset(); + }//end if + }//end if if ($paramGroupid === null) { $paramGroupid = $groupid; } + } else if ($paramGroupid !== null && $firstTagAfterParams === null) { + $firstTagAfterParams = $tag; }//end if $tagGroups[$groupid][] = $tag; }//end foreach foreach ($tagGroups as $groupid => $group) { - $maxLength = 0; - $paddings = []; + $maxLength = 0; + $paddings = []; + $canFixNonParamGroup = true; foreach ($group as $pos => $tag) { if ($paramGroupid === $groupid && $tokens[$tag]['content'] !== '@param' ) { $error = 'Tag %s cannot be grouped with parameter tags in a doc comment'; $data = [$tokens[$tag]['content']]; - $phpcsFile->addError($error, $tag, 'NonParamGroup', $data); - } + + if ($canFixNonParamGroup === true) { + $canFixNonParamGroup = $phpcsFile->addFixableError($error, $tag, 'NonParamGroup', $data); + } else { + $phpcsFile->addError($error, $tag, 'NonParamGroup', $data); + } + + if ($canFixNonParamGroup === true) { + $phpcsFile->fixer->beginChangeset(); + + if ($pos > 0) { + $thisTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $tag, $commentStart, false, $phpcsFile->eolChar)); + $phpcsFile->fixer->addContentBefore($thisTagLineStart, $indent.'*'.$phpcsFile->eolChar); + } else { + $nextTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($tag + 1), $commentEnd); + $nextTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $nextTag, $commentStart, false, $phpcsFile->eolChar)); + $phpcsFile->fixer->addContentBefore($nextTagLineStart, $indent.'*'.$phpcsFile->eolChar); + } + + $phpcsFile->fixer->endChangeset(); + $canFixNonParamGroup = false; + } + }//end if $tagLength = $tokens[$tag]['length']; if ($tagLength > $maxLength) { @@ -267,7 +384,7 @@ public function process(File $phpcsFile, $stackPtr) if ($string !== false && $tokens[$string]['line'] === $tokens[$tag]['line']) { $paddings[$tag] = $tokens[($tag + 1)]['length']; } - } + }//end foreach // Check that there was single blank line after the tag block // but account for a multi-line tag comments. @@ -288,7 +405,6 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->fixer->replaceToken($i, ''); } - $indent = str_repeat(' ', $tokens[$stackPtr]['column']); $phpcsFile->fixer->addContent($prev, $phpcsFile->eolChar.$indent.'*'.$phpcsFile->eolChar); $phpcsFile->fixer->endChangeset(); } @@ -318,8 +434,45 @@ public function process(File $phpcsFile, $stackPtr) // If there is a param group, it needs to be first. if ($paramGroupid !== null && $paramGroupid !== 0) { $error = 'Parameter tags must be defined first in a doc comment'; - $phpcsFile->addError($error, $tagGroups[$paramGroupid][0], 'ParamNotFirst'); - } + $fix = $phpcsFile->addFixableError($error, $tagGroups[$paramGroupid][0], 'ParamNotFirst'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $firstTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, $stackPtr, $commentEnd); + $firstParamTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($firstTag + 1), $commentEnd, false, '@param'); + if ($firstTagAfterParams === null) { + $firstTagAfterParams = $commentEnd; + } + + $lineBetween = ($tokens[$firstParamTag]['line'] - 1); + + $tagGroupOne = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $firstTag, $commentStart, false, $phpcsFile->eolChar)); + $tagGroupTwo = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $firstParamTag, $commentStart, false, $phpcsFile->eolChar)); + $markerThree = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $firstTagAfterParams, $commentStart, false, $phpcsFile->eolChar)); + + $otherContent = $tokens[$tagGroupOne]['content']; + for ($i = ($tagGroupOne + 1); $i < $tagGroupTwo; $i++) { + if ($tokens[$i]['line'] === $lineBetween) { + break; + } + + $otherContent .= $tokens[$i]['content']; + $phpcsFile->fixer->replaceToken($i, ''); + } + + $paramContent = $tokens[$tagGroupTwo]['content']; + for ($i = ($tagGroupTwo + 1); $i < $markerThree; $i++) { + $paramContent .= $tokens[$i]['content']; + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->replaceToken($tagGroupOne, $paramContent); + $phpcsFile->fixer->replaceToken($tagGroupTwo, $otherContent); + + $phpcsFile->fixer->endChangeset(); + }//end if + }//end if $foundTags = []; foreach ($tokens[$stackPtr]['comment_tags'] as $pos => $tag) { @@ -328,14 +481,46 @@ public function process(File $phpcsFile, $stackPtr) $lastTag = $tokens[$stackPtr]['comment_tags'][($pos - 1)]; if ($tokens[$lastTag]['content'] !== $tagName) { $error = 'Tags must be grouped together in a doc comment'; - $phpcsFile->addError($error, $tag, 'TagsNotGrouped'); - } + $fix = $phpcsFile->addFixableError($error, $tag, 'TagsNotGrouped'); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + + $prevTag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, ($tag - 1), $commentStart); + $prevTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $prevTag, $commentStart, false, $phpcsFile->eolChar)); + $thisTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $tag, $commentStart, false, $phpcsFile->eolChar)); + + if (isset($tokens[$stackPtr]['comment_tags'][($pos + 1)]) === true) { + $nextTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($tag + 1), $commentEnd); + $nextTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $nextTag, $commentStart, false, $phpcsFile->eolChar)); + } else { + $nextTagLineStart = (1 + $phpcsFile->findPrevious(T_DOC_COMMENT_WHITESPACE, $commentEnd, $commentStart, false, $phpcsFile->eolChar)); + } + + $prevTagContent = $tokens[$prevTagLineStart]['content']; + for ($i = ($prevTagLineStart + 1); $i < $thisTagLineStart; $i++) { + $prevTagContent .= $tokens[$i]['content']; + $phpcsFile->fixer->replaceToken($i, ''); + } + + $thisTagContent = $tokens[$thisTagLineStart]['content']; + for ($i = ($thisTagLineStart + 1); $i < $nextTagLineStart; $i++) { + $thisTagContent .= $tokens[$i]['content']; + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->replaceToken($prevTagLineStart, $thisTagContent); + $phpcsFile->fixer->replaceToken($thisTagLineStart, $prevTagContent); + + $phpcsFile->fixer->endChangeset(); + }//end if + }//end if continue; - } + }//end if $foundTags[$tagName] = true; - } + }//end foreach }//end process() diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc index 81366272c9..b4a9668a80 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc @@ -64,9 +64,9 @@ * Short description. * * - * @param + * @param one * - * @param + * @param two * * * @tag one @@ -163,6 +163,27 @@ * @three bar */ +/** + * @one + * @two + * @three + * @four + * @one + * @two + * @three + * @four + */ + +/** + * @one this has more than one line (1) + * and should remain with the first tag (1) + * @two a different tag which should be (2) + * moved to after the third tag (2) + * @one the same tag as the first but listed (3) + * after a different tag; this should be (3) + * moved above the second tag (3) + */ + /** * @ var Comment */ @@ -249,4 +270,8 @@ * @link http://pear.php.net/package/PHP_CodeSniffer */ +/** + * + */ + /** No docblock close tag. Must be last test without new line. \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed index 43ce064a5d..12c8719787 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed @@ -11,9 +11,9 @@ */ /** - * short description + * Short description * - * long description + * Long description * over multiple lines. * * @tag1 one @@ -44,10 +44,10 @@ /** * Short description. * - * @tag one - * * @param * @param + * + * @tag one */ /** @@ -55,15 +55,15 @@ * * @param * @param - * @tag one + * + * @tag one */ /** * Short description. * - * @param - * - * @param + * @param one + * @param two * * @tag one */ @@ -72,9 +72,9 @@ * Short description. * * @param - * - * @tag one * @param + * + * @tag one */ /** @@ -147,9 +147,9 @@ * Comment * * @one - * @two * @one * + * @two * @two something * here * @two foo @@ -158,6 +158,27 @@ * @three bar */ +/** + * @one + * @one + * @two + * @two + * @three + * @three + * @four + * @four + */ + +/** + * @one this has more than one line (1) + * and should remain with the first tag (1) + * @one the same tag as the first but listed (3) + * after a different tag; this should be (3) + * moved above the second tag (3) + * @two a different tag which should be (2) + * moved to after the third tag (2) + */ + /** * @ var Comment */ @@ -180,27 +201,31 @@ */ /** - * this is a test + * This is a test + * + * @param boolean $foo blah + * @param boolean $bar Blah. * * @author test - * @param boolean $foo blah + * * @return boolean - * @param boolean $bar Blah. */ /** * Short description. * + * @param int $number + * @param string $text + * * @tag one - * @param int $number - * @param string $text * @return something */ /** * - * @param int $number - * @param string $text + * @param int $number + * @param string $text + * * @return something */ @@ -209,11 +234,11 @@ */ /** - * étude des ... + * Étude des ... */ /** -* doc comment +* Doc comment */ /** @@ -221,7 +246,8 @@ * * @param * @param - * @tag one + * + * @tag one */ /** @@ -254,4 +280,5 @@ * @link http://pear.php.net/package/PHP_CodeSniffer */ + /** No docblock close tag. Must be last test without new line. \ No newline at end of file diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php index 57937581ba..4049cb39cc 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.php @@ -64,30 +64,38 @@ public function getErrorList() 95 => 1, 156 => 1, 158 => 1, - 170 => 3, - 171 => 3, - 179 => 1, - 183 => 1, - 184 => 2, - 185 => 1, - 186 => 1, - 187 => 2, - 193 => 1, - 196 => 1, - 199 => 1, - 203 => 1, + 166 => 1, + 171 => 1, + 172 => 1, + 173 => 1, + 174 => 1, + 177 => 1, + 182 => 1, + 191 => 3, + 192 => 3, + 200 => 1, + 204 => 1, + 205 => 2, 206 => 1, - 211 => 1, - 214 => 4, - 218 => 1, - 220 => 2, - 222 => 1, - 224 => 3, + 207 => 1, + 208 => 2, + 214 => 1, + 217 => 1, + 220 => 1, + 224 => 1, + 227 => 1, + 232 => 1, + 235 => 4, + 239 => 1, + 241 => 2, 243 => 1, - 244 => 1, - 246 => 1, - 248 => 1, - 249 => 1, + 245 => 3, + 264 => 1, + 265 => 1, + 267 => 1, + 269 => 1, + 270 => 1, + 273 => 1, ]; }//end getErrorList()