annotate vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php @ 0:4c8ae668cc8c

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