1<?php 2/** 3 * Generic_Sniffs_ControlStructures_InlineControlStructureSniff. 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 * Generic_Sniffs_ControlStructures_InlineControlStructureSniff. 18 * 19 * Verifies that inline control statements are not present. 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 Generic_Sniffs_ControlStructures_InlineControlStructureSniff 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 * If true, an error will be thrown; otherwise a warning. 45 * 46 * @var bool 47 */ 48 public $error = true; 49 50 51 /** 52 * Returns an array of tokens this test wants to listen for. 53 * 54 * @return array 55 */ 56 public function register() 57 { 58 return array( 59 T_IF, 60 T_ELSE, 61 T_ELSEIF, 62 T_FOREACH, 63 T_WHILE, 64 T_DO, 65 T_SWITCH, 66 T_FOR, 67 ); 68 69 }//end register() 70 71 72 /** 73 * Processes this test, when one of its tokens is encountered. 74 * 75 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 76 * @param int $stackPtr The position of the current token in the 77 * stack passed in $tokens. 78 * 79 * @return void 80 */ 81 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 82 { 83 $tokens = $phpcsFile->getTokens(); 84 85 if (isset($tokens[$stackPtr]['scope_opener']) === true) { 86 $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no'); 87 return; 88 } 89 90 // Ignore the ELSE in ELSE IF. We'll process the IF part later. 91 if ($tokens[$stackPtr]['code'] === T_ELSE) { 92 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 93 if ($tokens[$next]['code'] === T_IF) { 94 return; 95 } 96 } 97 98 if ($tokens[$stackPtr]['code'] === T_WHILE) { 99 // This could be from a DO WHILE, which doesn't have an opening brace. 100 $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 101 if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) { 102 $brace = $tokens[$lastContent]; 103 if (isset($brace['scope_condition']) === true) { 104 $condition = $tokens[$brace['scope_condition']]; 105 if ($condition['code'] === T_DO) { 106 return; 107 } 108 } 109 } 110 111 // In Javascript DO WHILE loops without curly braces are legal. This 112 // is only valid if a single statement is present between the DO and 113 // the WHILE. We can detect this by checking only a single semicolon 114 // is present between them. 115 if ($phpcsFile->tokenizerType === 'JS') { 116 $lastDo = $phpcsFile->findPrevious(T_DO, ($stackPtr - 1)); 117 $lastSemicolon = $phpcsFile->findPrevious(T_SEMICOLON, ($stackPtr - 1)); 118 if ($lastDo !== false && $lastSemicolon !== false && $lastDo < $lastSemicolon) { 119 $precedingSemicolon = $phpcsFile->findPrevious(T_SEMICOLON, ($lastSemicolon - 1)); 120 if ($precedingSemicolon === false || $precedingSemicolon < $lastDo) { 121 return; 122 } 123 } 124 } 125 }//end if 126 127 // This is a control structure without an opening brace, 128 // so it is an inline statement. 129 if ($this->error === true) { 130 $fix = $phpcsFile->addFixableError('Inline control structures are not allowed', $stackPtr, 'NotAllowed'); 131 } else { 132 $fix = $phpcsFile->addFixableWarning('Inline control structures are discouraged', $stackPtr, 'Discouraged'); 133 } 134 135 $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'yes'); 136 137 // Stop here if we are not fixing the error. 138 if ($fix !== true) { 139 return; 140 } 141 142 $phpcsFile->fixer->beginChangeset(); 143 if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) { 144 $closer = $tokens[$stackPtr]['parenthesis_closer']; 145 } else { 146 $closer = $stackPtr; 147 } 148 149 if ($tokens[($closer + 1)]['code'] === T_WHITESPACE 150 || $tokens[($closer + 1)]['code'] === T_SEMICOLON 151 ) { 152 $phpcsFile->fixer->addContent($closer, ' {'); 153 } else { 154 $phpcsFile->fixer->addContent($closer, ' { '); 155 } 156 157 $fixableScopeOpeners = $this->register(); 158 159 $lastNonEmpty = $closer; 160 for ($end = ($closer + 1); $end < $phpcsFile->numTokens; $end++) { 161 if ($tokens[$end]['code'] === T_SEMICOLON) { 162 break; 163 } 164 165 if ($tokens[$end]['code'] === T_CLOSE_TAG) { 166 $end = $lastNonEmpty; 167 break; 168 } 169 170 if (in_array($tokens[$end]['code'], $fixableScopeOpeners) === true 171 && isset($tokens[$end]['scope_opener']) === false 172 ) { 173 // The best way to fix nested inline scopes is middle-out. 174 // So skip this one. It will be detected and fixed on a future loop. 175 $phpcsFile->fixer->rollbackChangeset(); 176 return; 177 } 178 179 if (isset($tokens[$end]['scope_opener']) === true) { 180 $type = $tokens[$end]['code']; 181 $end = $tokens[$end]['scope_closer']; 182 if ($type === T_DO || $type === T_IF || $type === T_ELSEIF || $type === T_TRY) { 183 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), null, true); 184 if ($next === false) { 185 break; 186 } 187 188 $nextType = $tokens[$next]['code']; 189 190 // Let additional conditions loop and find their ending. 191 if (($type === T_IF 192 || $type === T_ELSEIF) 193 && ($nextType === T_ELSEIF 194 || $nextType === T_ELSE) 195 ) { 196 continue; 197 } 198 199 // Account for DO... WHILE conditions. 200 if ($type === T_DO && $nextType === T_WHILE) { 201 $end = $phpcsFile->findNext(T_SEMICOLON, ($next + 1)); 202 } 203 204 // Account for TRY... CATCH statements. 205 if ($type === T_TRY && $nextType === T_CATCH) { 206 $end = $tokens[$next]['scope_closer']; 207 } 208 }//end if 209 210 if ($tokens[$end]['code'] !== T_END_HEREDOC 211 && $tokens[$end]['code'] !== T_END_NOWDOC 212 ) { 213 break; 214 } 215 }//end if 216 217 if (isset($tokens[$end]['parenthesis_closer']) === true) { 218 $end = $tokens[$end]['parenthesis_closer']; 219 $lastNonEmpty = $end; 220 continue; 221 } 222 223 if ($tokens[$end]['code'] !== T_WHITESPACE) { 224 $lastNonEmpty = $end; 225 } 226 }//end for 227 228 if ($end === $phpcsFile->numTokens) { 229 $end = $lastNonEmpty; 230 } 231 232 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), null, true); 233 234 if ($next === false || $tokens[$next]['line'] !== $tokens[$end]['line']) { 235 // Looks for completely empty statements. 236 $next = $phpcsFile->findNext(T_WHITESPACE, ($closer + 1), ($end + 1), true); 237 238 // Account for a comment on the end of the line. 239 for ($endLine = $end; $endLine < $phpcsFile->numTokens; $endLine++) { 240 if (isset($tokens[($endLine + 1)]) === false 241 || $tokens[$endLine]['line'] !== $tokens[($endLine + 1)]['line'] 242 ) { 243 break; 244 } 245 } 246 247 if ($tokens[$endLine]['code'] !== T_COMMENT) { 248 $endLine = $end; 249 } 250 } else { 251 $next = ($end + 1); 252 $endLine = $end; 253 } 254 255 if ($next !== $end) { 256 if ($endLine !== $end) { 257 $endToken = $endLine; 258 $addedContent = ''; 259 } else { 260 $endToken = $end; 261 $addedContent = $phpcsFile->eolChar; 262 263 if ($tokens[$end]['code'] !== T_SEMICOLON 264 && $tokens[$end]['code'] !== T_CLOSE_CURLY_BRACKET 265 ) { 266 $phpcsFile->fixer->addContent($end, '; '); 267 } 268 } 269 270 $next = $phpcsFile->findNext(T_WHITESPACE, ($endToken + 1), null, true); 271 if ($next !== false 272 && ($tokens[$next]['code'] === T_ELSE 273 || $tokens[$next]['code'] === T_ELSEIF) 274 ) { 275 $phpcsFile->fixer->addContentBefore($next, '} '); 276 } else { 277 $indent = ''; 278 for ($first = $stackPtr; $first > 0; $first--) { 279 if ($first === 1 280 || $tokens[($first - 1)]['line'] !== $tokens[$first]['line'] 281 ) { 282 break; 283 } 284 } 285 286 if ($tokens[$first]['code'] === T_WHITESPACE) { 287 $indent = $tokens[$first]['content']; 288 } else if ($tokens[$first]['code'] === T_INLINE_HTML 289 || $tokens[$first]['code'] === T_OPEN_TAG 290 ) { 291 $addedContent = ''; 292 } 293 294 $addedContent .= $indent.'}'; 295 if ($next !== false && $tokens[$endToken]['code'] === T_COMMENT) { 296 $addedContent .= $phpcsFile->eolChar; 297 } 298 299 $phpcsFile->fixer->addContent($endToken, $addedContent); 300 }//end if 301 } else { 302 if ($endLine !== $end) { 303 $phpcsFile->fixer->replaceToken($end, ''); 304 $phpcsFile->fixer->addNewlineBefore($endLine); 305 $phpcsFile->fixer->addContent($endLine, '}'); 306 } else { 307 $phpcsFile->fixer->replaceToken($end, '}'); 308 } 309 }//end if 310 311 $phpcsFile->fixer->endChangeset(); 312 313 }//end process() 314 315 316}//end class 317