diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/TodoCommentSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/TodoCommentSniff.php new file mode 100644 index 00000000..f3d64718 --- /dev/null +++ b/coder_sniffer/Drupal/Sniffs/Commenting/TodoCommentSniff.php @@ -0,0 +1,157 @@ + + */ + public function register() + { + if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { + $this->debug = false; + } + + return [ + T_COMMENT, + T_DOC_COMMENT_TAG, + T_DOC_COMMENT_STRING, + ]; + + }//end register() + + + /** + * 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) + { + $debug = Config::getConfigData('todo_debug'); + if ($debug !== null) { + $this->debug = (bool) $debug; + } + + $tokens = $phpcsFile->getTokens(); + if ($this->debug === true) { + echo "\n------\n\$tokens[$stackPtr] = ".print_r($tokens[$stackPtr], true).PHP_EOL; + echo 'code = '.$tokens[$stackPtr]['code'].', type = '.$tokens[$stackPtr]['type']."\n"; + } + + // Standard comments and multi-line comments where the "@" is missing so + // it does not register as a T_DOC_COMMENT_TAG. + if ($tokens[$stackPtr]['code'] === T_COMMENT || $tokens[$stackPtr]['code'] === T_DOC_COMMENT_STRING) { + $comment = $tokens[$stackPtr]['content']; + if ($this->debug === true) { + echo "Getting \$comment from \$tokens[$stackPtr]['content']\n"; + } + + $this->checkTodoFormat($phpcsFile, $stackPtr, $comment); + } else if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_TAG) { + // Document comment tag (i.e. comments that begin with "@"). + // Determine if this is related at all and build the full comment line + // from the various segments that the line is parsed into. + $expression = '/^@to/i'; + $comment = $tokens[$stackPtr]['content']; + if ((bool) preg_match($expression, $comment) === true) { + if ($this->debug === true) { + echo "Attempting to build comment\n"; + } + + $index = ($stackPtr + 1); + while ($tokens[$index]['line'] === $tokens[$stackPtr]['line']) { + $comment .= $tokens[$index]['content']; + $index++; + } + + if ($this->debug === true) { + echo "Result comment = $comment\n"; + } + + $this->checkTodoFormat($phpcsFile, $stackPtr, $comment); + }//end if + }//end if + + }//end process() + + + /** + * Checks a comment string for the correct syntax. + * + * @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. + * @param string $comment The comment text. + * + * @return void + */ + private function checkTodoFormat(File $phpcsFile, $stackPtr, string $comment) + { + if ($this->debug === true) { + echo "Checking \$comment = '$comment'\n"; + } + + $expression = '/(?x) # Set free-space mode to allow this commenting + ^(\/|\s)* # At the start optionally match any forward slashes and spaces + (?i) # set case-insensitive mode + (?=( # start a positive non-consuming look-ahead to find all possible todos + @+to(-|\s|)+do # if one or more @ allow spaces and - between the to and do + | # or + to(-)*do # if no @ then only accept todo or to-do or to--do, etc, no spaces + )) + (?-i) # Reset to case-sensitive + (?! # Start another non-consuming look-ahead, this time negative + @todo\s # It has to match lower-case @todo followed by one space + (?!-|:)\S # and then any non-space except - or : + )/m'; + + if ((bool) preg_match($expression, $comment) === true) { + if ($this->debug === true) { + echo "Failed regex - give message\n"; + } + + $comment = trim($comment, " /\r\n"); + $phpcsFile->addWarning("'%s' should match the format '@todo Some task'", $stackPtr, 'TodoFormat', [$comment]); + } + + }//end checkTodoFormat() + + +}//end class diff --git a/tests/Drupal/Commenting/TodoCommentUnitTest.inc b/tests/Drupal/Commenting/TodoCommentUnitTest.inc new file mode 100644 index 00000000..0a4cdcc5 --- /dev/null +++ b/tests/Drupal/Commenting/TodoCommentUnitTest.inc @@ -0,0 +1,64 @@ + + */ + protected function getErrorList(string $testFile): array + { + return []; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @param string $testFile The name of the file being tested. + * + * @return array + */ + protected function getWarningList(string $testFile): array + { + $warningList = (array_fill_keys(range(13, 31), 1) + array_fill_keys(range(45, 63), 1)); + return $warningList; + + }//end getWarningList() + + +}//end class diff --git a/tests/Drupal/good/good.php b/tests/Drupal/good/good.php index 0efcf91c..2f36e561 100644 --- a/tests/Drupal/good/good.php +++ b/tests/Drupal/good/good.php @@ -1185,10 +1185,10 @@ function test6(array $names) { /** * Some short description. * - * @todo TODOs are allowed here. - * * @param string $x * Some parameter. + * + * @todo These are allowed here. */ function test7($x) { @@ -1205,8 +1205,8 @@ class ListContainsTest extends RulesIntegrationTestBase {} /** * Provides a 'Delete any path alias' action. * - * @todo: Add access callback information from Drupal 7. - * @todo: Add group information from Drupal 7. + * @todo Add access callback information from Drupal 7. + * @todo Add group information from Drupal 7. * * @Action( * id = "rules_path_alias_delete",