diff --git a/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php b/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php index 545319be5e..ac678a6404 100644 --- a/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php +++ b/src/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php @@ -71,9 +71,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']) { @@ -193,6 +215,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) @@ -210,7 +234,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(); } @@ -222,6 +245,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( @@ -244,28 +268,98 @@ 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++; + + if (($insertionPointer + 2) >= $commentEnd) { + $insertionPointer = ($commentEnd - 1); + break; + } + } + + $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) { @@ -277,7 +371,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. @@ -298,7 +392,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(); } @@ -328,8 +421,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) { @@ -338,14 +468,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..ba008adbec 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.inc.fixed @@ -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 */ @@ -182,25 +203,29 @@ /** * 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 */ @@ -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.js b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js index b3283f7853..0a8a9792ea 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js @@ -64,15 +64,15 @@ * Short description. * * - * @param + * @param one * - * @param + * @param two * * * @tag one */ - /** +/** * Short description. * * @param @@ -95,7 +95,7 @@ * @g3 two */ - /** +/** * Short description * over multiple lines. * @@ -117,7 +117,7 @@ * multiple lines */ - /** +/** * Returns true if the specified string is in the camel caps format. * * @param boolean $classFormat If true, check to see if the string is in the @@ -132,7 +132,7 @@ * @return boolean */ - /** +/** * Verifies that a @throws tag exists for a function that throws exceptions. * Verifies the number of @throws tags and the number of throw tokens matches. * Verifies the exception type. @@ -148,7 +148,7 @@ * @link http://pear.php.net/package/PHP_CodeSniffer */ - /** +/** * Comment * * @one @@ -163,7 +163,28 @@ * @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 */ @@ -248,3 +269,7 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence * @link http://pear.php.net/package/PHP_CodeSniffer */ + +/** + * + */ diff --git a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed index 0df0687a2f..1b42f61657 100644 --- a/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed +++ b/src/Standards/Generic/Tests/Commenting/DocCommentUnitTest.js.fixed @@ -44,10 +44,10 @@ /** * Short description. * - * @tag one - * * @param * @param + * + * @tag one */ /** @@ -55,26 +55,26 @@ * * @param * @param - * @tag one + * + * @tag one */ /** * Short description. * - * @param - * - * @param + * @param one + * @param two * * @tag one */ - /** +/** * Short description. * * @param - * - * @tag one * @param + * + * @tag one */ /** @@ -90,7 +90,7 @@ * @g3 two */ - /** +/** * Short description * over multiple lines. * @@ -112,7 +112,7 @@ * multiple lines */ - /** +/** * Returns true if the specified string is in the camel caps format. * * @param boolean $classFormat If true, check to see if the string is in the @@ -127,7 +127,7 @@ * @return boolean */ - /** +/** * Verifies that a @throws tag exists for a function that throws exceptions. * Verifies the number of @throws tags and the number of throw tokens matches. * Verifies the exception type. @@ -143,13 +143,13 @@ * @link http://pear.php.net/package/PHP_CodeSniffer */ - /** +/** * Comment * * @one - * @two * @one * + * @two * @two something * here * @two foo @@ -158,7 +158,28 @@ * @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 */ @@ -182,25 +203,29 @@ /** * 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 */ @@ -221,7 +246,8 @@ * * @param * @param - * @tag one + * + * @tag one */ /** @@ -253,3 +279,4 @@ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence * @link http://pear.php.net/package/PHP_CodeSniffer */ + 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()