annotate vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2 /**
Chris@0 3 * Parses and verifies the doc comments for files.
Chris@0 4 *
Chris@0 5 * @category PHP
Chris@0 6 * @package PHP_CodeSniffer
Chris@0 7 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@0 8 */
Chris@0 9
Chris@17 10 namespace Drupal\Sniffs\Commenting;
Chris@17 11
Chris@17 12 use PHP_CodeSniffer\Files\File;
Chris@17 13 use PHP_CodeSniffer\Sniffs\Sniff;
Chris@17 14
Chris@0 15 /**
Chris@0 16 * Parses and verifies the doc comments for files.
Chris@0 17 *
Chris@0 18 * Verifies that :
Chris@0 19 * <ul>
Chris@0 20 * <li>A doc comment exists.</li>
Chris@0 21 * <li>There is a blank newline after the @file statement.</li>
Chris@0 22 * </ul>
Chris@0 23 *
Chris@0 24 * @category PHP
Chris@0 25 * @package PHP_CodeSniffer
Chris@0 26 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@0 27 */
Chris@0 28
Chris@17 29 class FileCommentSniff implements Sniff
Chris@0 30 {
Chris@0 31
Chris@0 32
Chris@0 33 /**
Chris@0 34 * A list of tokenizers this sniff supports.
Chris@0 35 *
Chris@0 36 * @var array
Chris@0 37 */
Chris@0 38 public $supportedTokenizers = array(
Chris@0 39 'PHP',
Chris@0 40 'JS',
Chris@0 41 );
Chris@0 42
Chris@0 43
Chris@0 44 /**
Chris@0 45 * Returns an array of tokens this test wants to listen for.
Chris@0 46 *
Chris@0 47 * @return array
Chris@0 48 */
Chris@0 49 public function register()
Chris@0 50 {
Chris@0 51 return array(T_OPEN_TAG);
Chris@0 52
Chris@0 53 }//end register()
Chris@0 54
Chris@0 55
Chris@0 56 /**
Chris@0 57 * Processes this test, when one of its tokens is encountered.
Chris@0 58 *
Chris@17 59 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
Chris@17 60 * @param int $stackPtr The position of the current token
Chris@17 61 * in the stack passed in $tokens.
Chris@0 62 *
Chris@0 63 * @return int
Chris@0 64 */
Chris@17 65 public function process(File $phpcsFile, $stackPtr)
Chris@0 66 {
Chris@0 67 $this->currentFile = $phpcsFile;
Chris@0 68
Chris@0 69 $tokens = $phpcsFile->getTokens();
Chris@0 70 $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
Chris@0 71
Chris@0 72 // Files containing exactly one class, interface or trait are allowed to
Chris@0 73 // ommit a file doc block. If a namespace is used then the file comment must
Chris@0 74 // be omitted.
Chris@0 75 $oopKeyword = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT], $stackPtr);
Chris@0 76 if ($oopKeyword !== false) {
Chris@0 77 $namespace = $phpcsFile->findNext(T_NAMESPACE, $stackPtr);
Chris@0 78 // Check if the file contains multiple classes/interfaces/traits - then a
Chris@0 79 // file doc block is allowed.
Chris@0 80 $secondOopKeyword = $phpcsFile->findNext([T_CLASS, T_INTERFACE, T_TRAIT], ($oopKeyword + 1));
Chris@0 81 // Namespaced classes, interfaces and traits should not have an @file doc
Chris@0 82 // block.
Chris@0 83 if (($tokens[$commentStart]['code'] === T_DOC_COMMENT_OPEN_TAG
Chris@0 84 || $tokens[$commentStart]['code'] === T_COMMENT)
Chris@0 85 && $secondOopKeyword === false
Chris@0 86 && $namespace !== false
Chris@0 87 ) {
Chris@0 88 $fix = $phpcsFile->addFixableError('Namespaced classes, interfaces and traits should not begin with a file doc comment', $commentStart, 'NamespaceNoFileDoc');
Chris@0 89 if ($fix === true) {
Chris@0 90 $phpcsFile->fixer->beginChangeset();
Chris@0 91
Chris@0 92 for ($i = $commentStart; $i <= ($tokens[$commentStart]['comment_closer'] + 1); $i++) {
Chris@0 93 $phpcsFile->fixer->replaceToken($i, '');
Chris@0 94 }
Chris@0 95
Chris@0 96 // If, after removing the comment, there are two new lines
Chris@0 97 // remove them.
Chris@0 98 if ($tokens[($commentStart - 1)]['content'] === "\n" && $tokens[$i]['content'] === "\n") {
Chris@0 99 $phpcsFile->fixer->replaceToken($i, '');
Chris@0 100 }
Chris@0 101
Chris@0 102 $phpcsFile->fixer->endChangeset();
Chris@0 103 }
Chris@0 104 }
Chris@0 105
Chris@0 106 if ($namespace !== false) {
Chris@0 107 return ($phpcsFile->numTokens + 1);
Chris@0 108 }
Chris@0 109
Chris@0 110 // Search for global functions before and after the class.
Chris@0 111 $function = $phpcsFile->findPrevious(T_FUNCTION, ($oopKeyword - 1));
Chris@0 112 if ($function === false) {
Chris@0 113 $function = $phpcsFile->findNext(T_FUNCTION, ($tokens[$oopKeyword]['scope_closer'] + 1));
Chris@0 114 }
Chris@0 115
Chris@0 116 $fileTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($commentStart + 1), null, false, '@file');
Chris@0 117
Chris@0 118 // No other classes, no other global functions and no explicit @file tag
Chris@0 119 // anywhere means it is ok to skip the file comment.
Chris@0 120 if ($secondOopKeyword === false && $function === false && $fileTag === false) {
Chris@0 121 return ($phpcsFile->numTokens + 1);
Chris@0 122 }
Chris@0 123 }//end if
Chris@0 124
Chris@0 125 if ($tokens[$commentStart]['code'] === T_COMMENT) {
Chris@0 126 $fix = $phpcsFile->addFixableError('You must use "/**" style comments for a file comment', $commentStart, 'WrongStyle');
Chris@0 127 if ($fix === true) {
Chris@0 128 $content = $tokens[$commentStart]['content'];
Chris@0 129
Chris@0 130 // If the comment starts with something like "/**" then we just
Chris@0 131 // insert a space after the stars.
Chris@0 132 if (strpos($content, '/**') === 0) {
Chris@0 133 $phpcsFile->fixer->replaceToken($commentStart, str_replace('/**', '/** ', $content));
Chris@0 134 } else if (strpos($content, '/*') === 0) {
Chris@0 135 // Just turn the /* ... */ style comment into a /** ... */ style
Chris@0 136 // comment.
Chris@0 137 $phpcsFile->fixer->replaceToken($commentStart, str_replace('/*', '/**', $content));
Chris@0 138 } else {
Chris@0 139 $content = trim(ltrim($tokens[$commentStart]['content'], '/# '));
Chris@0 140 $phpcsFile->fixer->replaceToken($commentStart, "/**\n * @file\n * $content\n */\n");
Chris@0 141 }
Chris@0 142 }
Chris@0 143
Chris@0 144 return ($phpcsFile->numTokens + 1);
Chris@0 145 } else if ($commentStart === false || $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG) {
Chris@0 146 $fix = $phpcsFile->addFixableError('Missing file doc comment', 0, 'Missing');
Chris@0 147 if ($fix === true) {
Chris@0 148 // Only PHP has a real opening tag, additional newline at the
Chris@0 149 // beginning here.
Chris@0 150 if ($phpcsFile->tokenizerType === 'PHP') {
Chris@0 151 // In templates add the file doc block to the very beginning of
Chris@0 152 // the file.
Chris@0 153 if ($tokens[0]['code'] === T_INLINE_HTML) {
Chris@0 154 $phpcsFile->fixer->addContentBefore(0, "<?php\n\n/**\n * @file\n */\n?>\n");
Chris@0 155 } else {
Chris@0 156 $phpcsFile->fixer->addContent($stackPtr, "\n/**\n * @file\n */\n");
Chris@0 157 }
Chris@0 158 } else {
Chris@0 159 $phpcsFile->fixer->addContent($stackPtr, "/**\n * @file\n */\n");
Chris@0 160 }
Chris@0 161 }
Chris@0 162
Chris@0 163 return ($phpcsFile->numTokens + 1);
Chris@0 164 }//end if
Chris@0 165
Chris@0 166 $commentEnd = $tokens[$commentStart]['comment_closer'];
Chris@0 167 $fileTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($commentStart + 1), $commentEnd, false, '@file');
Chris@0 168 $next = $phpcsFile->findNext(T_WHITESPACE, ($commentEnd + 1), null, true);
Chris@0 169
Chris@0 170 // If there is no @file tag and the next line is a function or class
Chris@0 171 // definition then the file docblock is mising.
Chris@0 172 if ($tokens[$next]['line'] === ($tokens[$commentEnd]['line'] + 1)
Chris@0 173 && $tokens[$next]['code'] === T_FUNCTION
Chris@0 174 ) {
Chris@0 175 if ($fileTag === false) {
Chris@0 176 $fix = $phpcsFile->addFixableError('Missing file doc comment', $stackPtr, 'Missing');
Chris@0 177 if ($fix === true) {
Chris@0 178 // Only PHP has a real opening tag, additional newline at the
Chris@0 179 // beginning here.
Chris@0 180 if ($phpcsFile->tokenizerType === 'PHP') {
Chris@0 181 $phpcsFile->fixer->addContent($stackPtr, "\n/**\n * @file\n */\n");
Chris@0 182 } else {
Chris@0 183 $phpcsFile->fixer->addContent($stackPtr, "/**\n * @file\n */\n");
Chris@0 184 }
Chris@0 185 }
Chris@0 186
Chris@0 187 return ($phpcsFile->numTokens + 1);
Chris@0 188 }
Chris@0 189 }//end if
Chris@0 190
Chris@0 191 if ($fileTag === false || $tokens[$fileTag]['line'] !== ($tokens[$commentStart]['line'] + 1)) {
Chris@0 192 $second_line = $phpcsFile->findNext(array(T_DOC_COMMENT_STAR, T_DOC_COMMENT_CLOSE_TAG), ($commentStart + 1), $commentEnd);
Chris@0 193 $fix = $phpcsFile->addFixableError('The second line in the file doc comment must be "@file"', $second_line, 'FileTag');
Chris@0 194 if ($fix === true) {
Chris@0 195 if ($fileTag === false) {
Chris@0 196 $phpcsFile->fixer->addContent($commentStart, "\n * @file");
Chris@0 197 } else {
Chris@0 198 // Delete the @file tag at its current position and insert one
Chris@0 199 // after the beginning of the comment.
Chris@0 200 $phpcsFile->fixer->beginChangeset();
Chris@0 201 $phpcsFile->fixer->addContent($commentStart, "\n * @file");
Chris@0 202 $phpcsFile->fixer->replaceToken($fileTag, '');
Chris@0 203 $phpcsFile->fixer->endChangeset();
Chris@0 204 }
Chris@0 205 }
Chris@0 206
Chris@0 207 return ($phpcsFile->numTokens + 1);
Chris@0 208 }
Chris@0 209
Chris@0 210 // Exactly one blank line after the file comment.
Chris@0 211 if ($tokens[$next]['line'] !== ($tokens[$commentEnd]['line'] + 2)
Chris@0 212 && $next !== false && $tokens[$next]['code'] !== T_CLOSE_TAG
Chris@0 213 ) {
Chris@0 214 $error = 'There must be exactly one blank line after the file comment';
Chris@0 215 $fix = $phpcsFile->addFixableError($error, $commentEnd, 'SpacingAfterComment');
Chris@0 216 if ($fix === true) {
Chris@0 217 $phpcsFile->fixer->beginChangeset();
Chris@0 218 $uselessLine = ($commentEnd + 1);
Chris@0 219 while ($uselessLine < $next) {
Chris@0 220 $phpcsFile->fixer->replaceToken($uselessLine, '');
Chris@0 221 $uselessLine++;
Chris@0 222 }
Chris@0 223
Chris@0 224 $phpcsFile->fixer->addContent($commentEnd, "\n\n");
Chris@0 225 $phpcsFile->fixer->endChangeset();
Chris@0 226 }
Chris@0 227
Chris@0 228 return ($phpcsFile->numTokens + 1);
Chris@0 229 }
Chris@0 230
Chris@0 231 // Template file: no blank line after the file comment.
Chris@0 232 if ($tokens[$next]['line'] !== ($tokens[$commentEnd]['line'] + 1)
Chris@0 233 && $tokens[$next]['line'] > $tokens[$commentEnd]['line']
Chris@0 234 && $tokens[$next]['code'] === T_CLOSE_TAG
Chris@0 235 ) {
Chris@0 236 $error = 'There must be no blank line after the file comment in a template';
Chris@0 237 $fix = $phpcsFile->addFixableError($error, $commentEnd, 'TeamplateSpacingAfterComment');
Chris@0 238 if ($fix === true) {
Chris@0 239 $phpcsFile->fixer->beginChangeset();
Chris@0 240 $uselessLine = ($commentEnd + 1);
Chris@0 241 while ($uselessLine < $next) {
Chris@0 242 $phpcsFile->fixer->replaceToken($uselessLine, '');
Chris@0 243 $uselessLine++;
Chris@0 244 }
Chris@0 245
Chris@0 246 $phpcsFile->fixer->addContent($commentEnd, "\n");
Chris@0 247 $phpcsFile->fixer->endChangeset();
Chris@0 248 }
Chris@0 249 }
Chris@0 250
Chris@0 251 // Ignore the rest of the file.
Chris@0 252 return ($phpcsFile->numTokens + 1);
Chris@0 253
Chris@0 254 }//end process()
Chris@0 255
Chris@0 256
Chris@0 257 }//end class