1<?php 2/** 3 * Squiz_Sniffs_WhiteSpace_SuperfluousWhitespaceSniff. 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_WhiteSpace_SuperfluousWhitespaceSniff. 18 * 19 * Checks that no whitespace preceeds the first content of the file, exists 20 * after the last content of the file, resides after content on any line, or 21 * are two empty lines in functions. 22 * 23 * @category PHP 24 * @package PHP_CodeSniffer 25 * @author Greg Sherwood <gsherwood@squiz.net> 26 * @author Marc McIntyre <mmcintyre@squiz.net> 27 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 28 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 29 * @version Release: @package_version@ 30 * @link http://pear.php.net/package/PHP_CodeSniffer 31 */ 32class Squiz_Sniffs_WhiteSpace_SuperfluousWhitespaceSniff implements PHP_CodeSniffer_Sniff 33{ 34 35 /** 36 * A list of tokenizers this sniff supports. 37 * 38 * @var array 39 */ 40 public $supportedTokenizers = array( 41 'PHP', 42 'JS', 43 'CSS', 44 ); 45 46 /** 47 * If TRUE, whitespace rules are not checked for blank lines. 48 * 49 * Blank lines are those that contain only whitespace. 50 * 51 * @var boolean 52 */ 53 public $ignoreBlankLines = false; 54 55 56 /** 57 * Returns an array of tokens this test wants to listen for. 58 * 59 * @return array 60 */ 61 public function register() 62 { 63 return array( 64 T_OPEN_TAG, 65 T_CLOSE_TAG, 66 T_WHITESPACE, 67 T_COMMENT, 68 T_DOC_COMMENT_WHITESPACE, 69 T_CLOSURE, 70 ); 71 72 }//end register() 73 74 75 /** 76 * Processes this sniff, when one of its tokens is encountered. 77 * 78 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 79 * @param int $stackPtr The position of the current token in the 80 * stack passed in $tokens. 81 * 82 * @return void 83 */ 84 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 85 { 86 $tokens = $phpcsFile->getTokens(); 87 88 if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) { 89 /* 90 Check for start of file whitespace. 91 */ 92 93 if ($phpcsFile->tokenizerType !== 'PHP') { 94 // The first token is always the open tag inserted when tokenizsed 95 // and the second token is always the first piece of content in 96 // the file. If the second token is whitespace, there was 97 // whitespace at the start of the file. 98 if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { 99 return; 100 } 101 102 if ($phpcsFile->fixer->enabled === true) { 103 $stackPtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 104 } 105 } else { 106 // If it's the first token, then there is no space. 107 if ($stackPtr === 0) { 108 return; 109 } 110 111 for ($i = ($stackPtr - 1); $i >= 0; $i--) { 112 // If we find something that isn't inline html then there is something previous in the file. 113 if ($tokens[$i]['type'] !== 'T_INLINE_HTML') { 114 return; 115 } 116 117 // If we have ended up with inline html make sure it isn't just whitespace. 118 $tokenContent = trim($tokens[$i]['content']); 119 if ($tokenContent !== '') { 120 return; 121 } 122 } 123 }//end if 124 125 $fix = $phpcsFile->addFixableError('Additional whitespace found at start of file', $stackPtr, 'StartFile'); 126 if ($fix === true) { 127 $phpcsFile->fixer->beginChangeset(); 128 for ($i = 0; $i < $stackPtr; $i++) { 129 $phpcsFile->fixer->replaceToken($i, ''); 130 } 131 132 $phpcsFile->fixer->endChangeset(); 133 } 134 } else if ($tokens[$stackPtr]['code'] === T_CLOSE_TAG) { 135 /* 136 Check for end of file whitespace. 137 */ 138 139 if ($phpcsFile->tokenizerType === 'PHP') { 140 if (isset($tokens[($stackPtr + 1)]) === false) { 141 // The close PHP token is the last in the file. 142 return; 143 } 144 145 for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { 146 // If we find something that isn't inline HTML then there 147 // is more to the file. 148 if ($tokens[$i]['type'] !== 'T_INLINE_HTML') { 149 return; 150 } 151 152 // If we have ended up with inline html make sure it 153 // isn't just whitespace. 154 $tokenContent = trim($tokens[$i]['content']); 155 if (empty($tokenContent) === false) { 156 return; 157 } 158 } 159 } else { 160 // The last token is always the close tag inserted when tokenized 161 // and the second last token is always the last piece of content in 162 // the file. If the second last token is whitespace, there was 163 // whitespace at the end of the file. 164 $stackPtr--; 165 166 // The pointer is now looking at the last content in the file and 167 // not the fake PHP end tag the tokenizer inserted. 168 if ($tokens[$stackPtr]['code'] !== T_WHITESPACE) { 169 return; 170 } 171 172 // Allow a single newline at the end of the last line in the file. 173 if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE 174 && $tokens[$stackPtr]['content'] === $phpcsFile->eolChar 175 ) { 176 return; 177 } 178 179 if ($phpcsFile->fixer->enabled === true) { 180 $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 181 $stackPtr = ($prev + 1); 182 } 183 }//end if 184 185 $fix = $phpcsFile->addFixableError('Additional whitespace found at end of file', $stackPtr, 'EndFile'); 186 if ($fix === true) { 187 $phpcsFile->fixer->beginChangeset(); 188 for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { 189 $phpcsFile->fixer->replaceToken($i, ''); 190 } 191 192 $phpcsFile->fixer->endChangeset(); 193 } 194 } else { 195 /* 196 Check for end of line whitespace. 197 */ 198 199 // Ignore whitespace that is not at the end of a line. 200 if (isset($tokens[($stackPtr + 1)]['line']) === true 201 && $tokens[($stackPtr + 1)]['line'] === $tokens[$stackPtr]['line'] 202 ) { 203 return; 204 } 205 206 // Ignore blank lines if required. 207 if ($this->ignoreBlankLines === true 208 && $tokens[($stackPtr - 1)]['line'] !== $tokens[$stackPtr]['line'] 209 ) { 210 return; 211 } 212 213 $tokenContent = rtrim($tokens[$stackPtr]['content'], $phpcsFile->eolChar); 214 if (empty($tokenContent) === false) { 215 if ($tokenContent !== rtrim($tokenContent)) { 216 $fix = $phpcsFile->addFixableError('Whitespace found at end of line', $stackPtr, 'EndLine'); 217 if ($fix === true) { 218 $phpcsFile->fixer->replaceToken($stackPtr, rtrim($tokenContent).$phpcsFile->eolChar); 219 } 220 } 221 } else if ($tokens[($stackPtr - 1)]['content'] !== rtrim($tokens[($stackPtr - 1)]['content']) 222 && $tokens[($stackPtr - 1)]['line'] === $tokens[$stackPtr]['line'] 223 ) { 224 $fix = $phpcsFile->addFixableError('Whitespace found at end of line', ($stackPtr - 1), 'EndLine'); 225 if ($fix === true) { 226 $phpcsFile->fixer->replaceToken(($stackPtr - 1), rtrim($tokens[($stackPtr - 1)]['content'])); 227 } 228 } 229 230 /* 231 Check for multiple blank lines in a function. 232 */ 233 234 if (($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true 235 || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true) 236 && $tokens[($stackPtr - 1)]['line'] < $tokens[$stackPtr]['line'] 237 && $tokens[($stackPtr - 2)]['line'] === $tokens[($stackPtr - 1)]['line'] 238 ) { 239 // This is an empty line and the line before this one is not 240 // empty, so this could be the start of a multiple empty 241 // line block. 242 $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr, null, true); 243 $lines = ($tokens[$next]['line'] - $tokens[$stackPtr]['line']); 244 if ($lines > 1) { 245 $error = 'Functions must not contain multiple empty lines in a row; found %s empty lines'; 246 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'EmptyLines', array($lines)); 247 if ($fix === true) { 248 $phpcsFile->fixer->beginChangeset(); 249 $i = $stackPtr; 250 while ($tokens[$i]['line'] !== $tokens[$next]['line']) { 251 $phpcsFile->fixer->replaceToken($i, ''); 252 $i++; 253 } 254 255 $phpcsFile->fixer->addNewlineBefore($i); 256 $phpcsFile->fixer->endChangeset(); 257 } 258 } 259 }//end if 260 }//end if 261 262 }//end process() 263 264 265}//end class 266