1<?php 2/** 3 * PEAR_Sniffs_ControlStructures_MultiLineConditionSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 11 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 12 * @link http://pear.php.net/package/PHP_CodeSniffer 13 */ 14 15/** 16 * PEAR_Sniffs_ControlStructures_MultiLineConditionSniff. 17 * 18 * Ensure multi-line IF conditions are defined correctly. 19 * 20 * @category PHP 21 * @package PHP_CodeSniffer 22 * @author Greg Sherwood <gsherwood@squiz.net> 23 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 24 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 25 * @version Release: @package_version@ 26 * @link http://pear.php.net/package/PHP_CodeSniffer 27 */ 28class PEAR_Sniffs_ControlStructures_MultiLineConditionSniff implements PHP_CodeSniffer_Sniff 29{ 30 31 /** 32 * A list of tokenizers this sniff supports. 33 * 34 * @var array 35 */ 36 public $supportedTokenizers = array( 37 'PHP', 38 'JS', 39 ); 40 41 /** 42 * The number of spaces code should be indented. 43 * 44 * @var int 45 */ 46 public $indent = 4; 47 48 49 /** 50 * Returns an array of tokens this test wants to listen for. 51 * 52 * @return array 53 */ 54 public function register() 55 { 56 return array( 57 T_IF, 58 T_ELSEIF, 59 ); 60 61 }//end register() 62 63 64 /** 65 * Processes this test, when one of its tokens is encountered. 66 * 67 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 68 * @param int $stackPtr The position of the current token 69 * in the stack passed in $tokens. 70 * 71 * @return void 72 */ 73 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 74 { 75 $tokens = $phpcsFile->getTokens(); 76 77 if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) { 78 return; 79 } 80 81 $openBracket = $tokens[$stackPtr]['parenthesis_opener']; 82 $closeBracket = $tokens[$stackPtr]['parenthesis_closer']; 83 $spaceAfterOpen = 0; 84 if ($tokens[($openBracket + 1)]['code'] === T_WHITESPACE) { 85 if (strpos($tokens[($openBracket + 1)]['content'], $phpcsFile->eolChar) !== false) { 86 $spaceAfterOpen = 'newline'; 87 } else { 88 $spaceAfterOpen = strlen($tokens[($openBracket + 1)]['content']); 89 } 90 } 91 92 if ($spaceAfterOpen !== 0) { 93 $error = 'First condition of a multi-line IF statement must directly follow the opening parenthesis'; 94 $fix = $phpcsFile->addFixableError($error, ($openBracket + 1), 'SpacingAfterOpenBrace'); 95 if ($fix === true) { 96 if ($spaceAfterOpen === 'newline') { 97 $phpcsFile->fixer->replaceToken(($openBracket + 1), ''); 98 } else { 99 $phpcsFile->fixer->replaceToken(($openBracket + 1), ''); 100 } 101 } 102 } 103 104 // We need to work out how far indented the if statement 105 // itself is, so we can work out how far to indent conditions. 106 $statementIndent = 0; 107 for ($i = ($stackPtr - 1); $i >= 0; $i--) { 108 if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) { 109 $i++; 110 break; 111 } 112 } 113 114 if ($i >= 0 && $tokens[$i]['code'] === T_WHITESPACE) { 115 $statementIndent = strlen($tokens[$i]['content']); 116 } 117 118 // Each line between the parenthesis should be indented 4 spaces 119 // and start with an operator, unless the line is inside a 120 // function call, in which case it is ignored. 121 $prevLine = $tokens[$openBracket]['line']; 122 for ($i = ($openBracket + 1); $i <= $closeBracket; $i++) { 123 if ($i === $closeBracket && $tokens[$openBracket]['line'] !== $tokens[$i]['line']) { 124 $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($i - 1), null, true); 125 if ($tokens[$prev]['line'] === $tokens[$i]['line']) { 126 // Closing bracket is on the same line as a condition. 127 $error = 'Closing parenthesis of a multi-line IF statement must be on a new line'; 128 $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketNewLine'); 129 if ($fix === true) { 130 // Account for a comment at the end of the line. 131 $next = $phpcsFile->findNext(T_WHITESPACE, ($closeBracket + 1), null, true); 132 if ($tokens[$next]['code'] !== T_COMMENT) { 133 $phpcsFile->fixer->addNewlineBefore($closeBracket); 134 } else { 135 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), null, true); 136 $phpcsFile->fixer->beginChangeset(); 137 $phpcsFile->fixer->replaceToken($closeBracket, ''); 138 $phpcsFile->fixer->addContentBefore($next, ')'); 139 $phpcsFile->fixer->endChangeset(); 140 } 141 } 142 } 143 }//end if 144 145 if ($tokens[$i]['line'] !== $prevLine) { 146 if ($tokens[$i]['line'] === $tokens[$closeBracket]['line']) { 147 $next = $phpcsFile->findNext(T_WHITESPACE, $i, null, true); 148 if ($next !== $closeBracket) { 149 $expectedIndent = ($statementIndent + $this->indent); 150 } else { 151 // Closing brace needs to be indented to the same level 152 // as the statement. 153 $expectedIndent = $statementIndent; 154 }//end if 155 } else { 156 $expectedIndent = ($statementIndent + $this->indent); 157 }//end if 158 159 if ($tokens[$i]['code'] === T_COMMENT) { 160 $prevLine = $tokens[$i]['line']; 161 continue; 162 } 163 164 // We changed lines, so this should be a whitespace indent token. 165 if ($tokens[$i]['code'] !== T_WHITESPACE) { 166 $foundIndent = 0; 167 } else { 168 $foundIndent = strlen($tokens[$i]['content']); 169 } 170 171 if ($expectedIndent !== $foundIndent) { 172 $error = 'Multi-line IF statement not indented correctly; expected %s spaces but found %s'; 173 $data = array( 174 $expectedIndent, 175 $foundIndent, 176 ); 177 178 $fix = $phpcsFile->addFixableError($error, $i, 'Alignment', $data); 179 if ($fix === true) { 180 $spaces = str_repeat(' ', $expectedIndent); 181 if ($foundIndent === 0) { 182 $phpcsFile->fixer->addContentBefore($i, $spaces); 183 } else { 184 $phpcsFile->fixer->replaceToken($i, $spaces); 185 } 186 } 187 } 188 189 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $i, null, true); 190 if ($next !== $closeBracket) { 191 if (isset(PHP_CodeSniffer_Tokens::$booleanOperators[$tokens[$next]['code']]) === false) { 192 $error = 'Each line in a multi-line IF statement must begin with a boolean operator'; 193 $fix = $phpcsFile->addFixableError($error, $i, 'StartWithBoolean'); 194 if ($fix === true) { 195 $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($i - 1), $openBracket, true); 196 if (isset(PHP_CodeSniffer_Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { 197 $phpcsFile->fixer->beginChangeset(); 198 $phpcsFile->fixer->replaceToken($prev, ''); 199 $phpcsFile->fixer->addContentBefore($next, $tokens[$prev]['content'].' '); 200 $phpcsFile->fixer->endChangeset(); 201 } else { 202 for ($x = ($prev + 1); $x < $next; $x++) { 203 $phpcsFile->fixer->replaceToken($x, ''); 204 } 205 } 206 } 207 } 208 }//end if 209 210 $prevLine = $tokens[$i]['line']; 211 }//end if 212 213 if ($tokens[$i]['code'] === T_STRING) { 214 $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); 215 if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) { 216 // This is a function call, so skip to the end as they 217 // have their own indentation rules. 218 $i = $tokens[$next]['parenthesis_closer']; 219 $prevLine = $tokens[$i]['line']; 220 continue; 221 } 222 } 223 }//end for 224 225 // From here on, we are checking the spacing of the opening and closing 226 // braces. If this IF statement does not use braces, we end here. 227 if (isset($tokens[$stackPtr]['scope_opener']) === false) { 228 return; 229 } 230 231 // The opening brace needs to be one space away from the closing parenthesis. 232 $openBrace = $tokens[$stackPtr]['scope_opener']; 233 $next = $phpcsFile->findNext(T_WHITESPACE, ($closeBracket + 1), $openBrace, true); 234 if ($next !== false) { 235 // Probably comments in between tokens, so don't check. 236 return; 237 } 238 239 if ($tokens[$openBrace]['line'] > $tokens[$closeBracket]['line']) { 240 $length = -1; 241 } else if ($openBrace === ($closeBracket + 1)) { 242 $length = 0; 243 } else if ($openBrace === ($closeBracket + 2) 244 && $tokens[($closeBracket + 1)]['code'] === T_WHITESPACE 245 ) { 246 $length = strlen($tokens[($closeBracket + 1)]['content']); 247 } else { 248 // Confused, so don't check. 249 $length = 1; 250 } 251 252 if ($length === 1) { 253 return; 254 } 255 256 $data = array($length); 257 $code = 'SpaceBeforeOpenBrace'; 258 259 $error = 'There must be a single space between the closing parenthesis and the opening brace of a multi-line IF statement; found '; 260 if ($length === -1) { 261 $error .= 'newline'; 262 $code = 'NewlineBeforeOpenBrace'; 263 } else { 264 $error .= '%s spaces'; 265 } 266 267 $fix = $phpcsFile->addFixableError($error, ($closeBracket + 1), $code, $data); 268 if ($fix === true) { 269 if ($length === 0) { 270 $phpcsFile->fixer->addContent($closeBracket, ' '); 271 } else { 272 $phpcsFile->fixer->replaceToken(($closeBracket + 1), ' '); 273 } 274 } 275 276 }//end process() 277 278 279}//end class 280