1<?php 2/** 3 * Squiz_Sniffs_Commenting_InlineCommentSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @author Marc McIntyre <mmcintyre@squiz.net> 11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 12 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 13 * @link http://pear.php.net/package/PHP_CodeSniffer 14 */ 15 16/** 17 * Squiz_Sniffs_Commenting_InlineCommentSniff. 18 * 19 * Checks that there is adequate spacing between comments. 20 * 21 * @category PHP 22 * @package PHP_CodeSniffer 23 * @author Greg Sherwood <gsherwood@squiz.net> 24 * @author Marc McIntyre <mmcintyre@squiz.net> 25 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 26 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 27 * @version Release: @package_version@ 28 * @link http://pear.php.net/package/PHP_CodeSniffer 29 */ 30class Squiz_Sniffs_Commenting_InlineCommentSniff implements PHP_CodeSniffer_Sniff 31{ 32 33 /** 34 * A list of tokenizers this sniff supports. 35 * 36 * @var array 37 */ 38 public $supportedTokenizers = array( 39 'PHP', 40 'JS', 41 ); 42 43 44 /** 45 * Returns an array of tokens this test wants to listen for. 46 * 47 * @return array 48 */ 49 public function register() 50 { 51 return array( 52 T_COMMENT, 53 T_DOC_COMMENT_OPEN_TAG, 54 ); 55 56 }//end register() 57 58 59 /** 60 * Processes this test, when one of its tokens is encountered. 61 * 62 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 63 * @param int $stackPtr The position of the current token in the 64 * stack passed in $tokens. 65 * 66 * @return void 67 */ 68 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 69 { 70 $tokens = $phpcsFile->getTokens(); 71 72 // If this is a function/class/interface doc block comment, skip it. 73 // We are only interested in inline doc block comments, which are 74 // not allowed. 75 if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) { 76 $nextToken = $phpcsFile->findNext( 77 PHP_CodeSniffer_Tokens::$emptyTokens, 78 ($stackPtr + 1), 79 null, 80 true 81 ); 82 83 $ignore = array( 84 T_CLASS, 85 T_INTERFACE, 86 T_TRAIT, 87 T_FUNCTION, 88 T_CLOSURE, 89 T_PUBLIC, 90 T_PRIVATE, 91 T_PROTECTED, 92 T_FINAL, 93 T_STATIC, 94 T_ABSTRACT, 95 T_CONST, 96 T_PROPERTY, 97 T_INCLUDE, 98 T_INCLUDE_ONCE, 99 T_REQUIRE, 100 T_REQUIRE_ONCE, 101 ); 102 103 if (in_array($tokens[$nextToken]['code'], $ignore) === true) { 104 return; 105 } 106 107 if ($phpcsFile->tokenizerType === 'JS') { 108 // We allow block comments if a function or object 109 // is being assigned to a variable. 110 $ignore = PHP_CodeSniffer_Tokens::$emptyTokens; 111 $ignore[] = T_EQUAL; 112 $ignore[] = T_STRING; 113 $ignore[] = T_OBJECT_OPERATOR; 114 $nextToken = $phpcsFile->findNext($ignore, ($nextToken + 1), null, true); 115 if ($tokens[$nextToken]['code'] === T_FUNCTION 116 || $tokens[$nextToken]['code'] === T_CLOSURE 117 || $tokens[$nextToken]['code'] === T_OBJECT 118 || $tokens[$nextToken]['code'] === T_PROTOTYPE 119 ) { 120 return; 121 } 122 } 123 124 $prevToken = $phpcsFile->findPrevious( 125 PHP_CodeSniffer_Tokens::$emptyTokens, 126 ($stackPtr - 1), 127 null, 128 true 129 ); 130 131 if ($tokens[$prevToken]['code'] === T_OPEN_TAG) { 132 return; 133 } 134 135 if ($tokens[$stackPtr]['content'] === '/**') { 136 $error = 'Inline doc block comments are not allowed; use "/* Comment */" or "// Comment" instead'; 137 $phpcsFile->addError($error, $stackPtr, 'DocBlock'); 138 } 139 }//end if 140 141 if ($tokens[$stackPtr]['content']{0} === '#') { 142 $error = 'Perl-style comments are not allowed; use "// Comment" instead'; 143 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle'); 144 if ($fix === true) { 145 $comment = ltrim($tokens[$stackPtr]['content'], "# \t"); 146 $phpcsFile->fixer->replaceToken($stackPtr, "// $comment"); 147 } 148 } 149 150 // We don't want end of block comments. If the last comment is a closing 151 // curly brace. 152 $previousContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 153 if ($tokens[$previousContent]['line'] === $tokens[$stackPtr]['line']) { 154 if ($tokens[$previousContent]['code'] === T_CLOSE_CURLY_BRACKET) { 155 return; 156 } 157 158 // Special case for JS files. 159 if ($tokens[$previousContent]['code'] === T_COMMA 160 || $tokens[$previousContent]['code'] === T_SEMICOLON 161 ) { 162 $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($previousContent - 1), null, true); 163 if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) { 164 return; 165 } 166 } 167 } 168 169 $comment = rtrim($tokens[$stackPtr]['content']); 170 171 // Only want inline comments. 172 if (substr($comment, 0, 2) !== '//') { 173 return; 174 } 175 176 if (trim(substr($comment, 2)) !== '') { 177 $spaceCount = 0; 178 $tabFound = false; 179 180 $commentLength = strlen($comment); 181 for ($i = 2; $i < $commentLength; $i++) { 182 if ($comment[$i] === "\t") { 183 $tabFound = true; 184 break; 185 } 186 187 if ($comment[$i] !== ' ') { 188 break; 189 } 190 191 $spaceCount++; 192 } 193 194 $fix = false; 195 if ($tabFound === true) { 196 $error = 'Tab found before comment text; expected "// %s" but found "%s"'; 197 $data = array( 198 ltrim(substr($comment, 2)), 199 $comment, 200 ); 201 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'TabBefore', $data); 202 } else if ($spaceCount === 0) { 203 $error = 'No space found before comment text; expected "// %s" but found "%s"'; 204 $data = array( 205 substr($comment, 2), 206 $comment, 207 ); 208 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore', $data); 209 } else if ($spaceCount > 1) { 210 $error = 'Expected 1 space before comment text but found %s; use block comment if you need indentation'; 211 $data = array( 212 $spaceCount, 213 substr($comment, (2 + $spaceCount)), 214 $comment, 215 ); 216 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data); 217 }//end if 218 219 if ($fix === true) { 220 $newComment = '// '.ltrim($tokens[$stackPtr]['content'], "/\t "); 221 $phpcsFile->fixer->replaceToken($stackPtr, $newComment); 222 } 223 }//end if 224 225 // The below section determines if a comment block is correctly capitalised, 226 // and ends in a full-stop. It will find the last comment in a block, and 227 // work its way up. 228 $nextComment = $phpcsFile->findNext(array(T_COMMENT), ($stackPtr + 1), null, false); 229 if (($nextComment !== false) 230 && (($tokens[$nextComment]['line']) === ($tokens[$stackPtr]['line'] + 1)) 231 ) { 232 return; 233 } 234 235 $topComment = $stackPtr; 236 $lastComment = $stackPtr; 237 while (($topComment = $phpcsFile->findPrevious(array(T_COMMENT), ($lastComment - 1), null, false)) !== false) { 238 if ($tokens[$topComment]['line'] !== ($tokens[$lastComment]['line'] - 1)) { 239 break; 240 } 241 242 $lastComment = $topComment; 243 } 244 245 $topComment = $lastComment; 246 $commentText = ''; 247 248 for ($i = $topComment; $i <= $stackPtr; $i++) { 249 if ($tokens[$i]['code'] === T_COMMENT) { 250 $commentText .= trim(substr($tokens[$i]['content'], 2)); 251 } 252 } 253 254 if ($commentText === '') { 255 $error = 'Blank comments are not allowed'; 256 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); 257 if ($fix === true) { 258 $phpcsFile->fixer->replaceToken($stackPtr, ''); 259 } 260 261 return; 262 } 263 264 if (preg_match('/^\p{Ll}/u', $commentText) === 1) { 265 $error = 'Inline comments must start with a capital letter'; 266 $phpcsFile->addError($error, $topComment, 'NotCapital'); 267 } 268 269 // Only check the end of comment character if the start of the comment 270 // is a letter, indicating that the comment is just standard text. 271 if (preg_match('/^\p{L}/u', $commentText) === 1) { 272 $commentCloser = $commentText[(strlen($commentText) - 1)]; 273 $acceptedClosers = array( 274 'full-stops' => '.', 275 'exclamation marks' => '!', 276 'or question marks' => '?', 277 ); 278 279 if (in_array($commentCloser, $acceptedClosers) === false) { 280 $error = 'Inline comments must end in %s'; 281 $ender = ''; 282 foreach ($acceptedClosers as $closerName => $symbol) { 283 $ender .= ' '.$closerName.','; 284 } 285 286 $ender = trim($ender, ' ,'); 287 $data = array($ender); 288 $phpcsFile->addError($error, $stackPtr, 'InvalidEndChar', $data); 289 } 290 } 291 292 // Finally, the line below the last comment cannot be empty if this inline 293 // comment is on a line by itself. 294 if ($tokens[$previousContent]['line'] < $tokens[$stackPtr]['line']) { 295 $start = false; 296 for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { 297 if ($tokens[$i]['line'] === ($tokens[$stackPtr]['line'] + 1)) { 298 if ($tokens[$i]['code'] !== T_WHITESPACE) { 299 return; 300 } 301 } else if ($tokens[$i]['line'] > ($tokens[$stackPtr]['line'] + 1)) { 302 break; 303 } 304 } 305 306 $error = 'There must be no blank line following an inline comment'; 307 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfter'); 308 if ($fix === true) { 309 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 310 $phpcsFile->fixer->beginChangeset(); 311 for ($i = ($stackPtr + 1); $i < $next; $i++) { 312 if ($tokens[$i]['line'] === $tokens[$next]['line']) { 313 break; 314 } 315 316 $phpcsFile->fixer->replaceToken($i, ''); 317 } 318 319 $phpcsFile->fixer->endChangeset(); 320 } 321 }//end if 322 323 }//end process() 324 325 326}//end class 327