1<?php 2/** 3 * Squiz_Sniffs_WhiteSpace_ControlStructureSpacingSniff. 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_ControlStructureSpacingSniff. 18 * 19 * Checks that control structures have the correct spacing around brackets. 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_WhiteSpace_ControlStructureSpacingSniff 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_IF, 53 T_WHILE, 54 T_FOREACH, 55 T_FOR, 56 T_SWITCH, 57 T_DO, 58 T_ELSE, 59 T_ELSEIF, 60 T_TRY, 61 T_CATCH, 62 ); 63 64 }//end register() 65 66 67 /** 68 * Processes this test, when one of its tokens is encountered. 69 * 70 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 71 * @param int $stackPtr The position of the current token 72 * in the stack passed in $tokens. 73 * 74 * @return void 75 */ 76 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 77 { 78 $tokens = $phpcsFile->getTokens(); 79 80 if (isset($tokens[$stackPtr]['parenthesis_opener']) === true 81 && isset($tokens[$stackPtr]['parenthesis_closer']) === true 82 ) { 83 $parenOpener = $tokens[$stackPtr]['parenthesis_opener']; 84 $parenCloser = $tokens[$stackPtr]['parenthesis_closer']; 85 if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) { 86 $gap = $tokens[($parenOpener + 1)]['length']; 87 88 if ($gap === 0) { 89 $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', 'newline'); 90 $gap = 'newline'; 91 } else { 92 $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', $gap); 93 } 94 95 $error = 'Expected 0 spaces after opening bracket; %s found'; 96 $data = array($gap); 97 $fix = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data); 98 if ($fix === true) { 99 $phpcsFile->fixer->replaceToken(($parenOpener + 1), ''); 100 } 101 } else { 102 $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', 0); 103 } 104 105 if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line'] 106 && $tokens[($parenCloser - 1)]['code'] === T_WHITESPACE 107 ) { 108 $gap = $tokens[($parenCloser - 1)]['length']; 109 $error = 'Expected 0 spaces before closing bracket; %s found'; 110 $data = array($gap); 111 $fix = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data); 112 if ($fix === true) { 113 $phpcsFile->fixer->replaceToken(($parenCloser - 1), ''); 114 } 115 116 if ($gap === 0) { 117 $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', 'newline'); 118 } else { 119 $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', $gap); 120 } 121 } else { 122 $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', 0); 123 } 124 }//end if 125 126 if (isset($tokens[$stackPtr]['scope_closer']) === false) { 127 return; 128 } 129 130 $scopeOpener = $tokens[$stackPtr]['scope_opener']; 131 $scopeCloser = $tokens[$stackPtr]['scope_closer']; 132 133 for ($firstContent = ($scopeOpener + 1); $firstContent < $phpcsFile->numTokens; $firstContent++) { 134 if ($tokens[$firstContent]['code'] !== T_WHITESPACE) { 135 break; 136 } 137 } 138 139 // We ignore spacing for some structures that tend to have their own rules. 140 $ignore = array( 141 T_FUNCTION => true, 142 T_CLASS => true, 143 T_INTERFACE => true, 144 T_TRAIT => true, 145 T_DOC_COMMENT_OPEN_TAG => true, 146 ); 147 148 if (isset($ignore[$tokens[$firstContent]['code']]) === false 149 && $tokens[$firstContent]['line'] >= ($tokens[$scopeOpener]['line'] + 2) 150 ) { 151 $error = 'Blank line found at start of control structure'; 152 $fix = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingAfterOpen'); 153 154 if ($fix === true) { 155 $phpcsFile->fixer->beginChangeset(); 156 $i = ($scopeOpener + 1); 157 while ($tokens[$i]['line'] !== $tokens[$firstContent]['line']) { 158 $phpcsFile->fixer->replaceToken($i, ''); 159 $i++; 160 } 161 162 $phpcsFile->fixer->addNewline($scopeOpener); 163 $phpcsFile->fixer->endChangeset(); 164 } 165 } 166 167 if ($firstContent !== $scopeCloser) { 168 $lastContent = $phpcsFile->findPrevious( 169 T_WHITESPACE, 170 ($scopeCloser - 1), 171 null, 172 true 173 ); 174 175 $lastNonEmptyContent = $phpcsFile->findPrevious( 176 PHP_CodeSniffer_Tokens::$emptyTokens, 177 ($scopeCloser - 1), 178 null, 179 true 180 ); 181 182 $checkToken = $lastContent; 183 if (isset($tokens[$lastNonEmptyContent]['scope_condition']) === true) { 184 $checkToken = $tokens[$lastNonEmptyContent]['scope_condition']; 185 } 186 187 if (isset($ignore[$tokens[$checkToken]['code']]) === false 188 && $tokens[$lastContent]['line'] <= ($tokens[$scopeCloser]['line'] - 2) 189 ) { 190 $errorToken = $scopeCloser; 191 for ($i = ($scopeCloser - 1); $i > $lastContent; $i--) { 192 if ($tokens[$i]['line'] < $tokens[$scopeCloser]['line']) { 193 $errorToken = $i; 194 break; 195 } 196 } 197 198 $error = 'Blank line found at end of control structure'; 199 $fix = $phpcsFile->addFixableError($error, $errorToken, 'SpacingBeforeClose'); 200 201 if ($fix === true) { 202 $phpcsFile->fixer->beginChangeset(); 203 $i = ($scopeCloser - 1); 204 for ($i = ($scopeCloser - 1); $i > $lastContent; $i--) { 205 if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']) { 206 continue; 207 } 208 209 if ($tokens[$i]['line'] === $tokens[$lastContent]['line']) { 210 break; 211 } 212 213 $phpcsFile->fixer->replaceToken($i, ''); 214 } 215 216 $phpcsFile->fixer->endChangeset(); 217 } 218 }//end if 219 }//end if 220 221 $trailingContent = $phpcsFile->findNext( 222 T_WHITESPACE, 223 ($scopeCloser + 1), 224 null, 225 true 226 ); 227 228 if ($tokens[$trailingContent]['code'] === T_COMMENT) { 229 // Special exception for code where the comment about 230 // an ELSE or ELSEIF is written between the control structures. 231 $nextCode = $phpcsFile->findNext( 232 PHP_CodeSniffer_Tokens::$emptyTokens, 233 ($scopeCloser + 1), 234 null, 235 true 236 ); 237 238 if ($tokens[$nextCode]['code'] === T_ELSE 239 || $tokens[$nextCode]['code'] === T_ELSEIF 240 ) { 241 $trailingContent = $nextCode; 242 } 243 }//end if 244 245 if ($tokens[$trailingContent]['code'] === T_ELSE) { 246 if ($tokens[$stackPtr]['code'] === T_IF) { 247 // IF with ELSE. 248 return; 249 } 250 } 251 252 if ($tokens[$trailingContent]['code'] === T_WHILE 253 && $tokens[$stackPtr]['code'] === T_DO 254 ) { 255 // DO with WHILE. 256 return; 257 } 258 259 if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) { 260 // At the end of the script or embedded code. 261 return; 262 } 263 264 if (isset($tokens[$trailingContent]['scope_condition']) === true 265 && $tokens[$trailingContent]['scope_condition'] !== $trailingContent 266 && isset($tokens[$trailingContent]['scope_opener']) === true 267 && $tokens[$trailingContent]['scope_opener'] !== $trailingContent 268 ) { 269 // Another control structure's closing brace. 270 $owner = $tokens[$trailingContent]['scope_condition']; 271 if ($tokens[$owner]['code'] === T_FUNCTION) { 272 // The next content is the closing brace of a function 273 // so normal function rules apply and we can ignore it. 274 return; 275 } 276 277 if ($tokens[$owner]['code'] === T_CLOSURE 278 && ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true 279 || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true 280 || isset($tokens[$stackPtr]['nested_parenthesis']) === true) 281 ) { 282 return; 283 } 284 285 if ($tokens[$trailingContent]['line'] !== ($tokens[$scopeCloser]['line'] + 1)) { 286 $error = 'Blank line found after control structure'; 287 $fix = $phpcsFile->addFixableError($error, $scopeCloser, 'LineAfterClose'); 288 289 if ($fix === true) { 290 $phpcsFile->fixer->beginChangeset(); 291 $i = ($scopeCloser + 1); 292 while ($tokens[$i]['line'] !== $tokens[$trailingContent]['line']) { 293 $phpcsFile->fixer->replaceToken($i, ''); 294 $i++; 295 } 296 297 $phpcsFile->fixer->addNewline($scopeCloser); 298 $phpcsFile->fixer->endChangeset(); 299 } 300 } 301 } else if ($tokens[$trailingContent]['code'] !== T_ELSE 302 && $tokens[$trailingContent]['code'] !== T_ELSEIF 303 && $tokens[$trailingContent]['code'] !== T_CATCH 304 && $tokens[$trailingContent]['line'] === ($tokens[$scopeCloser]['line'] + 1) 305 ) { 306 $error = 'No blank line found after control structure'; 307 $fix = $phpcsFile->addFixableError($error, $scopeCloser, 'NoLineAfterClose'); 308 if ($fix === true) { 309 $phpcsFile->fixer->addNewline($scopeCloser); 310 } 311 }//end if 312 313 }//end process() 314 315 316}//end class 317