annotate vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/ScopeIndentSniff.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2 /**
Chris@0 3 * Drupal_Sniffs_Whitespace_ScopeIndentSniff.
Chris@0 4 *
Chris@0 5 * @category PHP
Chris@0 6 * @package PHP_CodeSniffer
Chris@0 7 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@0 8 */
Chris@0 9
Chris@0 10 /**
Chris@0 11 * Largely copied from Generic_Sniffs_Whitespace_ScopeIndentSniff, modified to make
Chris@0 12 * the exact mode working with comments and multi line statements.
Chris@0 13 *
Chris@0 14 * Checks that control structures are structured correctly, and their content
Chris@0 15 * is indented correctly. This sniff will throw errors if tabs are used
Chris@0 16 * for indentation rather than spaces.
Chris@0 17 *
Chris@0 18 * @category PHP
Chris@0 19 * @package PHP_CodeSniffer
Chris@0 20 * @link http://pear.php.net/package/PHP_CodeSniffer
Chris@0 21 */
Chris@0 22 class Drupal_Sniffs_WhiteSpace_ScopeIndentSniff implements PHP_CodeSniffer_Sniff
Chris@0 23 {
Chris@0 24
Chris@0 25 /**
Chris@0 26 * A list of tokenizers this sniff supports.
Chris@0 27 *
Chris@0 28 * @var array
Chris@0 29 */
Chris@0 30 public $supportedTokenizers = array('PHP');
Chris@0 31
Chris@0 32 /**
Chris@0 33 * The number of spaces code should be indented.
Chris@0 34 *
Chris@0 35 * @var int
Chris@0 36 */
Chris@0 37 public $indent = 2;
Chris@0 38
Chris@0 39 /**
Chris@0 40 * Does the indent need to be exactly right?
Chris@0 41 *
Chris@0 42 * If TRUE, indent needs to be exactly $indent spaces. If FALSE,
Chris@0 43 * indent needs to be at least $indent spaces (but can be more).
Chris@0 44 *
Chris@0 45 * @var bool
Chris@0 46 */
Chris@0 47 public $exact = true;
Chris@0 48
Chris@0 49 /**
Chris@0 50 * Should tabs be used for indenting?
Chris@0 51 *
Chris@0 52 * If TRUE, fixes will be made using tabs instead of spaces.
Chris@0 53 * The size of each tab is important, so it should be specified
Chris@0 54 * using the --tab-width CLI argument.
Chris@0 55 *
Chris@0 56 * @var bool
Chris@0 57 */
Chris@0 58 public $tabIndent = false;
Chris@0 59
Chris@0 60 /**
Chris@0 61 * The --tab-width CLI value that is being used.
Chris@0 62 *
Chris@0 63 * @var int
Chris@0 64 */
Chris@0 65 private $_tabWidth = null;
Chris@0 66
Chris@0 67 /**
Chris@0 68 * List of tokens not needing to be checked for indentation.
Chris@0 69 *
Chris@0 70 * Useful to allow Sniffs based on this to easily ignore/skip some
Chris@0 71 * tokens from verification. For example, inline HTML sections
Chris@0 72 * or PHP open/close tags can escape from here and have their own
Chris@0 73 * rules elsewhere.
Chris@0 74 *
Chris@0 75 * @var int[]
Chris@0 76 */
Chris@0 77 public $ignoreIndentationTokens = array();
Chris@0 78
Chris@0 79 /**
Chris@0 80 * List of tokens not needing to be checked for indentation.
Chris@0 81 *
Chris@0 82 * This is a cached copy of the public version of this var, which
Chris@0 83 * can be set in a ruleset file, and some core ignored tokens.
Chris@0 84 *
Chris@0 85 * @var int[]
Chris@0 86 */
Chris@0 87 private $_ignoreIndentationTokens = array();
Chris@0 88
Chris@0 89 /**
Chris@0 90 * Any scope openers that should not cause an indent.
Chris@0 91 *
Chris@0 92 * @var int[]
Chris@0 93 */
Chris@0 94 protected $nonIndentingScopes = array();
Chris@0 95
Chris@0 96 /**
Chris@0 97 * Show debug output for this sniff.
Chris@0 98 *
Chris@0 99 * @var bool
Chris@0 100 */
Chris@0 101 private $_debug = false;
Chris@0 102
Chris@0 103
Chris@0 104 /**
Chris@0 105 * Returns an array of tokens this test wants to listen for.
Chris@0 106 *
Chris@0 107 * @return array
Chris@0 108 */
Chris@0 109 public function register()
Chris@0 110 {
Chris@0 111 if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
Chris@0 112 $this->_debug = false;
Chris@0 113 }
Chris@0 114
Chris@0 115 return array(T_OPEN_TAG);
Chris@0 116
Chris@0 117 }//end register()
Chris@0 118
Chris@0 119
Chris@0 120 /**
Chris@0 121 * Processes this test, when one of its tokens is encountered.
Chris@0 122 *
Chris@0 123 * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
Chris@0 124 * @param int $stackPtr The position of the current token
Chris@0 125 * in the stack passed in $tokens.
Chris@0 126 *
Chris@0 127 * @return void
Chris@0 128 */
Chris@0 129 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
Chris@0 130 {
Chris@0 131 $debug = PHP_CodeSniffer::getConfigData('scope_indent_debug');
Chris@0 132 if ($debug !== null) {
Chris@0 133 $this->_debug = (bool) $debug;
Chris@0 134 }
Chris@0 135
Chris@0 136 if ($this->_tabWidth === null) {
Chris@0 137 $cliValues = $phpcsFile->phpcs->cli->getCommandLineValues();
Chris@0 138 if (isset($cliValues['tabWidth']) === false || $cliValues['tabWidth'] === 0) {
Chris@0 139 // We have no idea how wide tabs are, so assume 4 spaces for fixing.
Chris@0 140 // It shouldn't really matter because indent checks elsewhere in the
Chris@0 141 // standard should fix things up.
Chris@0 142 $this->_tabWidth = 4;
Chris@0 143 } else {
Chris@0 144 $this->_tabWidth = $cliValues['tabWidth'];
Chris@0 145 }
Chris@0 146 }
Chris@0 147
Chris@0 148 $currentIndent = 0;
Chris@0 149 $lastOpenTag = $stackPtr;
Chris@0 150 $lastCloseTag = null;
Chris@0 151 $openScopes = array();
Chris@0 152 $adjustments = array();
Chris@0 153 $setIndents = array();
Chris@0 154
Chris@0 155 $tokens = $phpcsFile->getTokens();
Chris@0 156 $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr);
Chris@0 157 $trimmed = ltrim($tokens[$first]['content']);
Chris@0 158 if ($trimmed === '') {
Chris@0 159 $currentIndent = ($tokens[$stackPtr]['column'] - 1);
Chris@0 160 } else {
Chris@0 161 $currentIndent = (strlen($tokens[$first]['content']) - strlen($trimmed));
Chris@0 162 }
Chris@0 163
Chris@0 164 if ($this->_debug === true) {
Chris@0 165 $line = $tokens[$stackPtr]['line'];
Chris@0 166 echo "Start with token $stackPtr on line $line with indent $currentIndent".PHP_EOL;
Chris@0 167 }
Chris@0 168
Chris@0 169 if (empty($this->_ignoreIndentationTokens) === true) {
Chris@0 170 $this->_ignoreIndentationTokens = array(T_INLINE_HTML => true);
Chris@0 171 foreach ($this->ignoreIndentationTokens as $token) {
Chris@0 172 if (is_int($token) === false) {
Chris@0 173 if (defined($token) === false) {
Chris@0 174 continue;
Chris@0 175 }
Chris@0 176
Chris@0 177 $token = constant($token);
Chris@0 178 }
Chris@0 179
Chris@0 180 $this->_ignoreIndentationTokens[$token] = true;
Chris@0 181 }
Chris@0 182 }//end if
Chris@0 183
Chris@0 184 $this->exact = (bool) $this->exact;
Chris@0 185 $this->tabIndent = (bool) $this->tabIndent;
Chris@0 186
Chris@0 187 for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) {
Chris@0 188 if ($i === false) {
Chris@0 189 // Something has gone very wrong; maybe a parse error.
Chris@0 190 break;
Chris@0 191 }
Chris@0 192
Chris@0 193 $checkToken = null;
Chris@0 194 $checkIndent = null;
Chris@0 195
Chris@0 196 $exact = (bool) $this->exact;
Chris@0 197 if ($exact === true && isset($tokens[$i]['nested_parenthesis']) === true) {
Chris@0 198 // Don't check indents exactly between parenthesis as they
Chris@0 199 // tend to have custom rules, such as with multi-line function calls
Chris@0 200 // and control structure conditions.
Chris@0 201 $exact = false;
Chris@0 202 }
Chris@0 203
Chris@0 204 // Detect line changes and figure out where the indent is.
Chris@0 205 if ($tokens[$i]['column'] === 1) {
Chris@0 206 $trimmed = ltrim($tokens[$i]['content']);
Chris@0 207 if ($trimmed === '') {
Chris@0 208 if (isset($tokens[($i + 1)]) === true
Chris@0 209 && $tokens[$i]['line'] === $tokens[($i + 1)]['line']
Chris@0 210 ) {
Chris@0 211 $checkToken = ($i + 1);
Chris@0 212 $tokenIndent = ($tokens[($i + 1)]['column'] - 1);
Chris@0 213 }
Chris@0 214 } else {
Chris@0 215 $checkToken = $i;
Chris@0 216 $tokenIndent = (strlen($tokens[$i]['content']) - strlen($trimmed));
Chris@0 217 }
Chris@0 218 }
Chris@0 219
Chris@0 220 // Closing parenthesis should just be indented to at least
Chris@0 221 // the same level as where they were opened (but can be more).
Chris@0 222 if (($checkToken !== null
Chris@0 223 && $tokens[$checkToken]['code'] === T_CLOSE_PARENTHESIS
Chris@0 224 && isset($tokens[$checkToken]['parenthesis_opener']) === true)
Chris@0 225 || ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS
Chris@0 226 && isset($tokens[$i]['parenthesis_opener']) === true)
Chris@0 227 ) {
Chris@0 228 if ($checkToken !== null) {
Chris@0 229 $parenCloser = $checkToken;
Chris@0 230 } else {
Chris@0 231 $parenCloser = $i;
Chris@0 232 }
Chris@0 233
Chris@0 234 if ($this->_debug === true) {
Chris@0 235 $line = $tokens[$i]['line'];
Chris@0 236 echo "Closing parenthesis found on line $line".PHP_EOL;
Chris@0 237 }
Chris@0 238
Chris@0 239 $parenOpener = $tokens[$parenCloser]['parenthesis_opener'];
Chris@0 240 if ($tokens[$parenCloser]['line'] !== $tokens[$parenOpener]['line']) {
Chris@0 241 $parens = 0;
Chris@0 242 if (isset($tokens[$parenCloser]['nested_parenthesis']) === true
Chris@0 243 && empty($tokens[$parenCloser]['nested_parenthesis']) === false
Chris@0 244 ) {
Chris@0 245 end($tokens[$parenCloser]['nested_parenthesis']);
Chris@0 246 $parens = key($tokens[$parenCloser]['nested_parenthesis']);
Chris@0 247 if ($this->_debug === true) {
Chris@0 248 $line = $tokens[$parens]['line'];
Chris@0 249 echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL;
Chris@0 250 }
Chris@0 251 }
Chris@0 252
Chris@0 253 $condition = 0;
Chris@0 254 if (isset($tokens[$parenCloser]['conditions']) === true
Chris@0 255 && empty($tokens[$parenCloser]['conditions']) === false
Chris@0 256 ) {
Chris@0 257 end($tokens[$parenCloser]['conditions']);
Chris@0 258 $condition = key($tokens[$parenCloser]['conditions']);
Chris@0 259 if ($this->_debug === true) {
Chris@0 260 $line = $tokens[$condition]['line'];
Chris@0 261 $type = $tokens[$condition]['type'];
Chris@0 262 echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL;
Chris@0 263 }
Chris@0 264 }
Chris@0 265
Chris@0 266 if ($parens > $condition) {
Chris@0 267 if ($this->_debug === true) {
Chris@0 268 echo "\t* using parenthesis *".PHP_EOL;
Chris@0 269 }
Chris@0 270
Chris@0 271 $parenOpener = $parens;
Chris@0 272 $condition = 0;
Chris@0 273 } else if ($condition > 0) {
Chris@0 274 if ($this->_debug === true) {
Chris@0 275 echo "\t* using condition *".PHP_EOL;
Chris@0 276 }
Chris@0 277
Chris@0 278 $parenOpener = $condition;
Chris@0 279 $parens = 0;
Chris@0 280 }
Chris@0 281
Chris@0 282 $exact = false;
Chris@0 283
Chris@0 284 $lastOpenTagConditions = array_keys($tokens[$lastOpenTag]['conditions']);
Chris@0 285 $lastOpenTagCondition = array_pop($lastOpenTagConditions);
Chris@0 286
Chris@0 287 if ($condition > 0 && $lastOpenTagCondition === $condition) {
Chris@0 288 if ($this->_debug === true) {
Chris@0 289 echo "\t* open tag is inside condition; using open tag *".PHP_EOL;
Chris@0 290 }
Chris@0 291
Chris@0 292 $checkIndent = ($tokens[$lastOpenTag]['column'] - 1);
Chris@0 293 if (isset($adjustments[$condition]) === true) {
Chris@0 294 $checkIndent += $adjustments[$condition];
Chris@0 295 }
Chris@0 296
Chris@0 297 $currentIndent = $checkIndent;
Chris@0 298
Chris@0 299 if ($this->_debug === true) {
Chris@0 300 $type = $tokens[$lastOpenTag]['type'];
Chris@0 301 echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $lastOpenTag ($type)".PHP_EOL;
Chris@0 302 }
Chris@0 303 } else if ($condition > 0
Chris@0 304 && isset($tokens[$condition]['scope_opener']) === true
Chris@0 305 && isset($setIndents[$tokens[$condition]['scope_opener']]) === true
Chris@0 306 ) {
Chris@0 307 $checkIndent = $setIndents[$tokens[$condition]['scope_opener']];
Chris@0 308 if (isset($adjustments[$condition]) === true) {
Chris@0 309 $checkIndent += $adjustments[$condition];
Chris@0 310 }
Chris@0 311
Chris@0 312 $currentIndent = $checkIndent;
Chris@0 313
Chris@0 314 if ($this->_debug === true) {
Chris@0 315 $type = $tokens[$condition]['type'];
Chris@0 316 echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $condition ($type)".PHP_EOL;
Chris@0 317 }
Chris@0 318 } else {
Chris@0 319 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parenOpener, true);
Chris@0 320
Chris@0 321 $checkIndent = ($tokens[$first]['column'] - 1);
Chris@0 322 if (isset($adjustments[$first]) === true) {
Chris@0 323 $checkIndent += $adjustments[$first];
Chris@0 324 }
Chris@0 325
Chris@0 326 if ($this->_debug === true) {
Chris@0 327 $line = $tokens[$first]['line'];
Chris@0 328 $type = $tokens[$first]['type'];
Chris@0 329 echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
Chris@0 330 }
Chris@0 331
Chris@0 332 if ($first === $tokens[$parenCloser]['parenthesis_opener']) {
Chris@0 333 // This is unlikely to be the start of the statement, so look
Chris@0 334 // back further to find it.
Chris@0 335 $first--;
Chris@0 336 }
Chris@0 337
Chris@0 338 $prev = $phpcsFile->findStartOfStatement($first, T_COMMA);
Chris@0 339 if ($prev !== $first) {
Chris@0 340 // This is not the start of the statement.
Chris@0 341 if ($this->_debug === true) {
Chris@0 342 $line = $tokens[$prev]['line'];
Chris@0 343 $type = $tokens[$prev]['type'];
Chris@0 344 echo "\t* previous is $type on line $line *".PHP_EOL;
Chris@0 345 }
Chris@0 346
Chris@0 347 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
Chris@0 348 $prev = $phpcsFile->findStartOfStatement($first, T_COMMA);
Chris@0 349 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
Chris@0 350 if ($this->_debug === true) {
Chris@0 351 $line = $tokens[$first]['line'];
Chris@0 352 $type = $tokens[$first]['type'];
Chris@0 353 echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
Chris@0 354 }
Chris@0 355 }
Chris@0 356
Chris@0 357 if (isset($tokens[$first]['scope_closer']) === true
Chris@0 358 && $tokens[$first]['scope_closer'] === $first
Chris@0 359 ) {
Chris@0 360 if ($this->_debug === true) {
Chris@0 361 echo "\t* first token is a scope closer *".PHP_EOL;
Chris@0 362 }
Chris@0 363
Chris@0 364 if (isset($tokens[$first]['scope_condition']) === true) {
Chris@0 365 $scopeCloser = $first;
Chris@0 366 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true);
Chris@0 367
Chris@0 368 $currentIndent = ($tokens[$first]['column'] - 1);
Chris@0 369 if (isset($adjustments[$first]) === true) {
Chris@0 370 $currentIndent += $adjustments[$first];
Chris@0 371 }
Chris@0 372
Chris@0 373 // Make sure it is divisible by our expected indent.
Chris@0 374 if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) {
Chris@0 375 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 376 }
Chris@0 377
Chris@0 378 $setIndents[$first] = $currentIndent;
Chris@0 379
Chris@0 380 if ($this->_debug === true) {
Chris@0 381 $type = $tokens[$first]['type'];
Chris@0 382 echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL;
Chris@0 383 }
Chris@0 384 }//end if
Chris@0 385 } else {
Chris@0 386 // Don't force current indent to divisible because there could be custom
Chris@0 387 // rules in place between parenthesis, such as with arrays.
Chris@0 388 $currentIndent = ($tokens[$first]['column'] - 1);
Chris@0 389 if (isset($adjustments[$first]) === true) {
Chris@0 390 $currentIndent += $adjustments[$first];
Chris@0 391 }
Chris@0 392
Chris@0 393 $setIndents[$first] = $currentIndent;
Chris@0 394
Chris@0 395 if ($this->_debug === true) {
Chris@0 396 $type = $tokens[$first]['type'];
Chris@0 397 echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL;
Chris@0 398 }
Chris@0 399 }//end if
Chris@0 400 }//end if
Chris@0 401 } else if ($this->_debug === true) {
Chris@0 402 echo "\t * ignoring single-line definition *".PHP_EOL;
Chris@0 403 }//end if
Chris@0 404 }//end if
Chris@0 405
Chris@0 406 // Closing short array bracket should just be indented to at least
Chris@0 407 // the same level as where it was opened (but can be more).
Chris@0 408 if ($tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
Chris@0 409 || ($checkToken !== null
Chris@0 410 && $tokens[$checkToken]['code'] === T_CLOSE_SHORT_ARRAY)
Chris@0 411 ) {
Chris@0 412 if ($checkToken !== null) {
Chris@0 413 $arrayCloser = $checkToken;
Chris@0 414 } else {
Chris@0 415 $arrayCloser = $i;
Chris@0 416 }
Chris@0 417
Chris@0 418 if ($this->_debug === true) {
Chris@0 419 $line = $tokens[$arrayCloser]['line'];
Chris@0 420 echo "Closing short array bracket found on line $line".PHP_EOL;
Chris@0 421 }
Chris@0 422
Chris@0 423 $arrayOpener = $tokens[$arrayCloser]['bracket_opener'];
Chris@0 424 if ($tokens[$arrayCloser]['line'] !== $tokens[$arrayOpener]['line']) {
Chris@0 425 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $arrayOpener, true);
Chris@0 426 $checkIndent = ($tokens[$first]['column'] - 1);
Chris@0 427 if (isset($adjustments[$first]) === true) {
Chris@0 428 $checkIndent += $adjustments[$first];
Chris@0 429 }
Chris@0 430
Chris@0 431 $exact = false;
Chris@0 432
Chris@0 433 if ($this->_debug === true) {
Chris@0 434 $line = $tokens[$first]['line'];
Chris@0 435 $type = $tokens[$first]['type'];
Chris@0 436 echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
Chris@0 437 }
Chris@0 438
Chris@0 439 if ($first === $tokens[$arrayCloser]['bracket_opener']) {
Chris@0 440 // This is unlikely to be the start of the statement, so look
Chris@0 441 // back further to find it.
Chris@0 442 $first--;
Chris@0 443 }
Chris@0 444
Chris@0 445 $prev = $phpcsFile->findStartOfStatement($first, T_COMMA);
Chris@0 446 if ($prev !== $first) {
Chris@0 447 // This is not the start of the statement.
Chris@0 448 if ($this->_debug === true) {
Chris@0 449 $line = $tokens[$prev]['line'];
Chris@0 450 $type = $tokens[$prev]['type'];
Chris@0 451 echo "\t* previous is $type on line $line *".PHP_EOL;
Chris@0 452 }
Chris@0 453
Chris@0 454 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
Chris@0 455 $prev = $phpcsFile->findStartOfStatement($first, T_COMMA);
Chris@0 456 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
Chris@0 457 if ($this->_debug === true) {
Chris@0 458 $line = $tokens[$first]['line'];
Chris@0 459 $type = $tokens[$first]['type'];
Chris@0 460 echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
Chris@0 461 }
Chris@0 462 }
Chris@0 463
Chris@0 464 if (isset($tokens[$first]['scope_closer']) === true
Chris@0 465 && $tokens[$first]['scope_closer'] === $first
Chris@0 466 ) {
Chris@0 467 // The first token is a scope closer and would have already
Chris@0 468 // been processed and set the indent level correctly, so
Chris@0 469 // don't adjust it again.
Chris@0 470 if ($this->_debug === true) {
Chris@0 471 echo "\t* first token is a scope closer; ignoring closing short array bracket *".PHP_EOL;
Chris@0 472 }
Chris@0 473
Chris@0 474 if (isset($setIndents[$first]) === true) {
Chris@0 475 $currentIndent = $setIndents[$first];
Chris@0 476 if ($this->_debug === true) {
Chris@0 477 echo "\t=> indent reset to $currentIndent".PHP_EOL;
Chris@0 478 }
Chris@0 479 }
Chris@0 480 } else {
Chris@0 481 // Don't force current indent to be divisible because there could be custom
Chris@0 482 // rules in place for arrays.
Chris@0 483 $currentIndent = ($tokens[$first]['column'] - 1);
Chris@0 484 if (isset($adjustments[$first]) === true) {
Chris@0 485 $currentIndent += $adjustments[$first];
Chris@0 486 }
Chris@0 487
Chris@0 488 $setIndents[$first] = $currentIndent;
Chris@0 489
Chris@0 490 if ($this->_debug === true) {
Chris@0 491 $type = $tokens[$first]['type'];
Chris@0 492 echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL;
Chris@0 493 }
Chris@0 494 }//end if
Chris@0 495 } else if ($this->_debug === true) {
Chris@0 496 echo "\t * ignoring single-line definition *".PHP_EOL;
Chris@0 497 }//end if
Chris@0 498 }//end if
Chris@0 499
Chris@0 500 // Adjust lines within scopes while auto-fixing.
Chris@0 501 if ($checkToken !== null
Chris@0 502 && $exact === false
Chris@0 503 && (empty($tokens[$checkToken]['conditions']) === false
Chris@0 504 || (isset($tokens[$checkToken]['scope_opener']) === true
Chris@0 505 && $tokens[$checkToken]['scope_opener'] === $checkToken))
Chris@0 506 ) {
Chris@0 507 if (empty($tokens[$checkToken]['conditions']) === false) {
Chris@0 508 end($tokens[$checkToken]['conditions']);
Chris@0 509 $condition = key($tokens[$checkToken]['conditions']);
Chris@0 510 } else {
Chris@0 511 $condition = $tokens[$checkToken]['scope_condition'];
Chris@0 512 }
Chris@0 513
Chris@0 514 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true);
Chris@0 515
Chris@0 516 if (isset($adjustments[$first]) === true
Chris@0 517 && (($adjustments[$first] < 0 && $tokenIndent > $currentIndent)
Chris@0 518 || ($adjustments[$first] > 0 && $tokenIndent < $currentIndent))
Chris@0 519 ) {
Chris@0 520 $padding = ($tokenIndent + $adjustments[$first]);
Chris@0 521 if ($padding > 0) {
Chris@0 522 if ($this->tabIndent === true) {
Chris@0 523 $numTabs = floor($padding / $this->_tabWidth);
Chris@0 524 $numSpaces = ($padding - ($numTabs * $this->_tabWidth));
Chris@0 525 $padding = str_repeat("\t", $numTabs).str_repeat(' ', $numSpaces);
Chris@0 526 } else {
Chris@0 527 $padding = str_repeat(' ', $padding);
Chris@0 528 }
Chris@0 529 } else {
Chris@0 530 $padding = '';
Chris@0 531 }
Chris@0 532
Chris@0 533 if ($checkToken === $i) {
Chris@0 534 $phpcsFile->fixer->replaceToken($checkToken, $padding.$trimmed);
Chris@0 535 } else {
Chris@0 536 // Easier to just replace the entire indent.
Chris@0 537 $phpcsFile->fixer->replaceToken(($checkToken - 1), $padding);
Chris@0 538 }
Chris@0 539
Chris@0 540 if ($this->_debug === true) {
Chris@0 541 $length = strlen($padding);
Chris@0 542 $line = $tokens[$checkToken]['line'];
Chris@0 543 $type = $tokens[$checkToken]['type'];
Chris@0 544 echo "Indent adjusted to $length for $type on line $line".PHP_EOL;
Chris@0 545 }
Chris@0 546
Chris@0 547 $adjustments[$checkToken] = $adjustments[$first];
Chris@0 548
Chris@0 549 if ($this->_debug === true) {
Chris@0 550 $line = $tokens[$checkToken]['line'];
Chris@0 551 $type = $tokens[$checkToken]['type'];
Chris@0 552 echo "\t=> Add adjustment of ".$adjustments[$checkToken]." for token $checkToken ($type) on line $line".PHP_EOL;
Chris@0 553 }
Chris@0 554 }//end if
Chris@0 555 }//end if
Chris@0 556
Chris@0 557 // Scope closers reset the required indent to the same level as the opening condition.
Chris@0 558 if (($checkToken !== null
Chris@0 559 && isset($openScopes[$checkToken]) === true
Chris@0 560 || (isset($tokens[$checkToken]['scope_condition']) === true
Chris@0 561 && isset($tokens[$checkToken]['scope_closer']) === true
Chris@0 562 && $tokens[$checkToken]['scope_closer'] === $checkToken
Chris@0 563 && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['scope_opener']]['line']))
Chris@0 564 || ($checkToken === null
Chris@0 565 && isset($openScopes[$i]) === true
Chris@0 566 || (isset($tokens[$i]['scope_condition']) === true
Chris@0 567 && isset($tokens[$i]['scope_closer']) === true
Chris@0 568 && $tokens[$i]['scope_closer'] === $i
Chris@0 569 && $tokens[$i]['line'] !== $tokens[$tokens[$i]['scope_opener']]['line']))
Chris@0 570 ) {
Chris@0 571 if ($this->_debug === true) {
Chris@0 572 if ($checkToken === null) {
Chris@0 573 $type = $tokens[$tokens[$i]['scope_condition']]['type'];
Chris@0 574 $line = $tokens[$i]['line'];
Chris@0 575 } else {
Chris@0 576 $type = $tokens[$tokens[$checkToken]['scope_condition']]['type'];
Chris@0 577 $line = $tokens[$checkToken]['line'];
Chris@0 578 }
Chris@0 579
Chris@0 580 echo "Close scope ($type) on line $line".PHP_EOL;
Chris@0 581 }
Chris@0 582
Chris@0 583 $scopeCloser = $checkToken;
Chris@0 584 if ($scopeCloser === null) {
Chris@0 585 $scopeCloser = $i;
Chris@0 586 } else {
Chris@0 587 array_pop($openScopes);
Chris@0 588 }
Chris@0 589
Chris@0 590 if (isset($tokens[$scopeCloser]['scope_condition']) === true) {
Chris@0 591 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true);
Chris@0 592
Chris@0 593 $currentIndent = ($tokens[$first]['column'] - 1);
Chris@0 594 if (isset($adjustments[$first]) === true) {
Chris@0 595 $currentIndent += $adjustments[$first];
Chris@0 596 }
Chris@0 597
Chris@0 598 // Make sure it is divisible by our expected indent.
Chris@0 599 if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) {
Chris@0 600 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 601 }
Chris@0 602
Chris@0 603 $setIndents[$scopeCloser] = $currentIndent;
Chris@0 604
Chris@0 605 if ($this->_debug === true) {
Chris@0 606 $type = $tokens[$scopeCloser]['type'];
Chris@0 607 echo "\t=> indent set to $currentIndent by token $scopeCloser ($type)".PHP_EOL;
Chris@0 608 }
Chris@0 609
Chris@0 610 // We only check the indent of scope closers if they are
Chris@0 611 // curly braces because other constructs tend to have different rules.
Chris@0 612 if ($tokens[$scopeCloser]['code'] === T_CLOSE_CURLY_BRACKET) {
Chris@0 613 $exact = true;
Chris@0 614 } else {
Chris@0 615 $checkToken = null;
Chris@0 616 }
Chris@0 617 }//end if
Chris@0 618 }//end if
Chris@0 619
Chris@0 620 // Handle scope for JS object notation.
Chris@0 621 if ($phpcsFile->tokenizerType === 'JS'
Chris@0 622 && (($checkToken !== null
Chris@0 623 && $tokens[$checkToken]['code'] === T_CLOSE_OBJECT
Chris@0 624 && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['bracket_opener']]['line'])
Chris@0 625 || ($checkToken === null
Chris@0 626 && $tokens[$i]['code'] === T_CLOSE_OBJECT
Chris@0 627 && $tokens[$i]['line'] !== $tokens[$tokens[$i]['bracket_opener']]['line']))
Chris@0 628 ) {
Chris@0 629 if ($this->_debug === true) {
Chris@0 630 $line = $tokens[$i]['line'];
Chris@0 631 echo "Close JS object on line $line".PHP_EOL;
Chris@0 632 }
Chris@0 633
Chris@0 634 $scopeCloser = $checkToken;
Chris@0 635 if ($scopeCloser === null) {
Chris@0 636 $scopeCloser = $i;
Chris@0 637 } else {
Chris@0 638 array_pop($openScopes);
Chris@0 639 }
Chris@0 640
Chris@0 641 $parens = 0;
Chris@0 642 if (isset($tokens[$scopeCloser]['nested_parenthesis']) === true
Chris@0 643 && empty($tokens[$scopeCloser]['nested_parenthesis']) === false
Chris@0 644 ) {
Chris@0 645 end($tokens[$scopeCloser]['nested_parenthesis']);
Chris@0 646 $parens = key($tokens[$scopeCloser]['nested_parenthesis']);
Chris@0 647 if ($this->_debug === true) {
Chris@0 648 $line = $tokens[$parens]['line'];
Chris@0 649 echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL;
Chris@0 650 }
Chris@0 651 }
Chris@0 652
Chris@0 653 $condition = 0;
Chris@0 654 if (isset($tokens[$scopeCloser]['conditions']) === true
Chris@0 655 && empty($tokens[$scopeCloser]['conditions']) === false
Chris@0 656 ) {
Chris@0 657 end($tokens[$scopeCloser]['conditions']);
Chris@0 658 $condition = key($tokens[$scopeCloser]['conditions']);
Chris@0 659 if ($this->_debug === true) {
Chris@0 660 $line = $tokens[$condition]['line'];
Chris@0 661 $type = $tokens[$condition]['type'];
Chris@0 662 echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL;
Chris@0 663 }
Chris@0 664 }
Chris@0 665
Chris@0 666 if ($parens > $condition) {
Chris@0 667 if ($this->_debug === true) {
Chris@0 668 echo "\t* using parenthesis *".PHP_EOL;
Chris@0 669 }
Chris@0 670
Chris@0 671 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parens, true);
Chris@0 672 $condition = 0;
Chris@0 673 } else if ($condition > 0) {
Chris@0 674 if ($this->_debug === true) {
Chris@0 675 echo "\t* using condition *".PHP_EOL;
Chris@0 676 }
Chris@0 677
Chris@0 678 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true);
Chris@0 679 $parens = 0;
Chris@0 680 } else {
Chris@0 681 if ($this->_debug === true) {
Chris@0 682 $line = $tokens[$tokens[$scopeCloser]['bracket_opener']]['line'];
Chris@0 683 echo "\t* token is not in parenthesis or condition; using opener on line $line *".PHP_EOL;
Chris@0 684 }
Chris@0 685
Chris@0 686 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['bracket_opener'], true);
Chris@0 687 }//end if
Chris@0 688
Chris@0 689 $currentIndent = ($tokens[$first]['column'] - 1);
Chris@0 690 if (isset($adjustments[$first]) === true) {
Chris@0 691 $currentIndent += $adjustments[$first];
Chris@0 692 }
Chris@0 693
Chris@0 694 if ($parens > 0 || $condition > 0) {
Chris@0 695 $checkIndent = ($tokens[$first]['column'] - 1);
Chris@0 696 if (isset($adjustments[$first]) === true) {
Chris@0 697 $checkIndent += $adjustments[$first];
Chris@0 698 }
Chris@0 699
Chris@0 700 if ($condition > 0) {
Chris@0 701 $checkIndent += $this->indent;
Chris@0 702 $currentIndent += $this->indent;
Chris@0 703 $exact = true;
Chris@0 704 }
Chris@0 705 } else {
Chris@0 706 $checkIndent = $currentIndent;
Chris@0 707 }
Chris@0 708
Chris@0 709 // Make sure it is divisible by our expected indent.
Chris@0 710 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 711 $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent);
Chris@0 712 $setIndents[$first] = $currentIndent;
Chris@0 713
Chris@0 714 if ($this->_debug === true) {
Chris@0 715 $type = $tokens[$first]['type'];
Chris@0 716 echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL;
Chris@0 717 }
Chris@0 718 }//end if
Chris@0 719
Chris@0 720 if ($checkToken !== null
Chris@0 721 && isset(PHP_CodeSniffer_Tokens::$scopeOpeners[$tokens[$checkToken]['code']]) === true
Chris@0 722 && in_array($tokens[$checkToken]['code'], $this->nonIndentingScopes) === false
Chris@0 723 && isset($tokens[$checkToken]['scope_opener']) === true
Chris@0 724 ) {
Chris@0 725 $exact = true;
Chris@0 726
Chris@0 727 $lastOpener = null;
Chris@0 728 if (empty($openScopes) === false) {
Chris@0 729 end($openScopes);
Chris@0 730 $lastOpener = current($openScopes);
Chris@0 731 }
Chris@0 732
Chris@0 733 // A scope opener that shares a closer with another token (like multiple
Chris@0 734 // CASEs using the same BREAK) needs to reduce the indent level so its
Chris@0 735 // indent is checked correctly. It will then increase the indent again
Chris@0 736 // (as all openers do) after being checked.
Chris@0 737 if ($lastOpener !== null
Chris@0 738 && isset($tokens[$lastOpener]['scope_closer']) === true
Chris@0 739 && $tokens[$lastOpener]['level'] === $tokens[$checkToken]['level']
Chris@0 740 && $tokens[$lastOpener]['scope_closer'] === $tokens[$checkToken]['scope_closer']
Chris@0 741 ) {
Chris@0 742 $currentIndent -= $this->indent;
Chris@0 743 $setIndents[$lastOpener] = $currentIndent;
Chris@0 744 if ($this->_debug === true) {
Chris@0 745 $line = $tokens[$i]['line'];
Chris@0 746 $type = $tokens[$lastOpener]['type'];
Chris@0 747 echo "Shared closer found on line $line".PHP_EOL;
Chris@0 748 echo "\t=> indent set to $currentIndent by token $lastOpener ($type)".PHP_EOL;
Chris@0 749 }
Chris@0 750 }
Chris@0 751
Chris@0 752 if ($tokens[$checkToken]['code'] === T_CLOSURE
Chris@0 753 && $tokenIndent > $currentIndent
Chris@0 754 ) {
Chris@0 755 // The opener is indented more than needed, which is fine.
Chris@0 756 // But just check that it is divisible by our expected indent.
Chris@0 757 $checkIndent = (int) (ceil($tokenIndent / $this->indent) * $this->indent);
Chris@0 758 $exact = false;
Chris@0 759
Chris@0 760 if ($this->_debug === true) {
Chris@0 761 $line = $tokens[$i]['line'];
Chris@0 762 echo "Closure found on line $line".PHP_EOL;
Chris@0 763 echo "\t=> checking indent of $checkIndent; main indent remains at $currentIndent".PHP_EOL;
Chris@0 764 }
Chris@0 765 }
Chris@0 766 }//end if
Chris@0 767
Chris@0 768 // Method prefix indentation has to be exact or else if will break
Chris@0 769 // the rest of the function declaration, and potentially future ones.
Chris@0 770 if ($checkToken !== null
Chris@0 771 && isset(PHP_CodeSniffer_Tokens::$methodPrefixes[$tokens[$checkToken]['code']]) === true
Chris@0 772 && $tokens[($checkToken + 1)]['code'] !== T_DOUBLE_COLON
Chris@0 773 ) {
Chris@0 774 $exact = true;
Chris@0 775 }
Chris@0 776
Chris@0 777 // JS property indentation has to be exact or else if will break
Chris@0 778 // things like function and object indentation.
Chris@0 779 if ($checkToken !== null && $tokens[$checkToken]['code'] === T_PROPERTY) {
Chris@0 780 $exact = true;
Chris@0 781 }
Chris@0 782
Chris@0 783 // PHP tags needs to be indented to exact column positions
Chris@0 784 // so they don't cause problems with indent checks for the code
Chris@0 785 // within them, but they don't need to line up with the current indent.
Chris@0 786 if ($checkToken !== null
Chris@0 787 && ($tokens[$checkToken]['code'] === T_OPEN_TAG
Chris@0 788 || $tokens[$checkToken]['code'] === T_OPEN_TAG_WITH_ECHO
Chris@0 789 || $tokens[$checkToken]['code'] === T_CLOSE_TAG)
Chris@0 790 ) {
Chris@0 791 $exact = true;
Chris@0 792 $checkIndent = ($tokens[$checkToken]['column'] - 1);
Chris@0 793 $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent);
Chris@0 794 }
Chris@0 795
Chris@0 796 // Check the line indent.
Chris@0 797 if ($checkIndent === null) {
Chris@0 798 $checkIndent = $currentIndent;
Chris@0 799 }
Chris@0 800
Chris@0 801 // If the line starts with "->" we assume this is an indented chained
Chris@0 802 // method invocation, so we add one level of indentation.
Chris@0 803 if ($checkToken !== null && $tokens[$checkToken]['code'] === T_OBJECT_OPERATOR) {
Chris@0 804 $checkIndent += $this->indent;
Chris@0 805 }
Chris@0 806
Chris@0 807 // Comments starting with a star have an extra whitespace.
Chris@0 808 if ($checkToken !== null && $tokens[$checkToken]['code'] === T_COMMENT) {
Chris@0 809 $content = trim($tokens[$checkToken]['content']);
Chris@0 810 if ($content{0} === '*') {
Chris@0 811 $checkIndent += 1;
Chris@0 812 }
Chris@0 813 }
Chris@0 814
Chris@0 815 $adjusted = false;
Chris@0 816 if ($checkToken !== null
Chris@0 817 && isset($this->_ignoreIndentationTokens[$tokens[$checkToken]['code']]) === false
Chris@0 818 && (($tokenIndent !== $checkIndent && $exact === true)
Chris@0 819 || ($tokenIndent < $checkIndent && $exact === false))
Chris@0 820 ) {
Chris@0 821 if ($tokenIndent > $checkIndent) {
Chris@0 822 // Ignore multi line statements.
Chris@0 823 $before = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($checkToken - 1), null, true);
Chris@0 824 if ($before !== false && in_array(
Chris@0 825 $tokens[$before]['code'],
Chris@0 826 array(
Chris@0 827 T_SEMICOLON,
Chris@0 828 T_CLOSE_CURLY_BRACKET,
Chris@0 829 T_OPEN_CURLY_BRACKET,
Chris@0 830 T_COLON,
Chris@0 831 )
Chris@0 832 ) === false
Chris@0 833 ) {
Chris@0 834 continue;
Chris@0 835 }
Chris@0 836 }
Chris@0 837
Chris@0 838 // Skip array closing indentation errors, this is handled by the
Chris@0 839 // ArraySniff.
Chris@0 840 if (($tokens[$checkToken]['code'] === T_CLOSE_PARENTHESIS
Chris@0 841 && isset($tokens[$checkToken]['parenthesis_owner']) === true
Chris@0 842 && $tokens[$tokens[$checkToken]['parenthesis_owner']]['code'] === T_ARRAY)
Chris@0 843 || $tokens[$checkToken]['code'] === T_CLOSE_SHORT_ARRAY
Chris@0 844 ) {
Chris@0 845 continue;
Chris@0 846 }
Chris@0 847
Chris@0 848 $type = 'IncorrectExact';
Chris@0 849 $error = 'Line indented incorrectly; expected ';
Chris@0 850 if ($exact === false) {
Chris@0 851 $error .= 'at least ';
Chris@0 852 $type = 'Incorrect';
Chris@0 853 }
Chris@0 854
Chris@0 855 if ($this->tabIndent === true) {
Chris@0 856 $error .= '%s tabs, found %s';
Chris@0 857 $data = array(
Chris@0 858 floor($checkIndent / $this->_tabWidth),
Chris@0 859 floor($tokenIndent / $this->_tabWidth),
Chris@0 860 );
Chris@0 861 } else {
Chris@0 862 $error .= '%s spaces, found %s';
Chris@0 863 $data = array(
Chris@0 864 $checkIndent,
Chris@0 865 $tokenIndent,
Chris@0 866 );
Chris@0 867 }
Chris@0 868
Chris@0 869 if ($this->_debug === true) {
Chris@0 870 $line = $tokens[$checkToken]['line'];
Chris@0 871 $message = vsprintf($error, $data);
Chris@0 872 echo "[Line $line] $message".PHP_EOL;
Chris@0 873 }
Chris@0 874
Chris@0 875 $fix = $phpcsFile->addFixableError($error, $checkToken, $type, $data);
Chris@0 876 if ($fix === true || $this->_debug === true) {
Chris@0 877 $padding = '';
Chris@0 878 if ($this->tabIndent === true) {
Chris@0 879 $numTabs = floor($checkIndent / $this->_tabWidth);
Chris@0 880 if ($numTabs > 0) {
Chris@0 881 $numSpaces = ($checkIndent - ($numTabs * $this->_tabWidth));
Chris@0 882 $padding = str_repeat("\t", $numTabs).str_repeat(' ', $numSpaces);
Chris@0 883 }
Chris@0 884 } else if ($checkIndent > 0) {
Chris@0 885 $padding = str_repeat(' ', $checkIndent);
Chris@0 886 }
Chris@0 887
Chris@0 888 if ($checkToken === $i) {
Chris@0 889 $accepted = $phpcsFile->fixer->replaceToken($checkToken, $padding.$trimmed);
Chris@0 890 } else {
Chris@0 891 // Easier to just replace the entire indent.
Chris@0 892 $accepted = $phpcsFile->fixer->replaceToken(($checkToken - 1), $padding);
Chris@0 893 }
Chris@0 894
Chris@0 895 if ($accepted === true) {
Chris@0 896 $adjustments[$checkToken] = ($checkIndent - $tokenIndent);
Chris@0 897 if ($this->_debug === true) {
Chris@0 898 $line = $tokens[$checkToken]['line'];
Chris@0 899 $type = $tokens[$checkToken]['type'];
Chris@0 900 echo "\t=> Add adjustment of ".$adjustments[$checkToken]." for token $checkToken ($type) on line $line".PHP_EOL;
Chris@0 901 }
Chris@0 902 }
Chris@0 903 } else {
Chris@0 904 // Assume the change would be applied and continue
Chris@0 905 // checking indents under this assumption. This gives more
Chris@0 906 // technically accurate error messages.
Chris@0 907 $adjustments[$checkToken] = ($checkIndent - $tokenIndent);
Chris@0 908 }//end if
Chris@0 909 }//end if
Chris@0 910
Chris@0 911 if ($checkToken !== null) {
Chris@0 912 $i = $checkToken;
Chris@0 913 }
Chris@0 914
Chris@0 915 // Completely skip here/now docs as the indent is a part of the
Chris@0 916 // content itself.
Chris@0 917 if ($tokens[$i]['code'] === T_START_HEREDOC
Chris@0 918 || $tokens[$i]['code'] === T_START_NOWDOC
Chris@0 919 ) {
Chris@0 920 $i = $phpcsFile->findNext(array(T_END_HEREDOC, T_END_NOWDOC), ($i + 1));
Chris@0 921 continue;
Chris@0 922 }
Chris@0 923
Chris@0 924 // Completely skip multi-line strings as the indent is a part of the
Chris@0 925 // content itself.
Chris@0 926 if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING
Chris@0 927 || $tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING
Chris@0 928 ) {
Chris@0 929 $i = $phpcsFile->findNext($tokens[$i]['code'], ($i + 1), null, true);
Chris@0 930 $i--;
Chris@0 931 continue;
Chris@0 932 }
Chris@0 933
Chris@0 934 // Completely skip doc comments as they tend to have complex
Chris@0 935 // indentation rules.
Chris@0 936 if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG) {
Chris@0 937 $i = $tokens[$i]['comment_closer'];
Chris@0 938 continue;
Chris@0 939 }
Chris@0 940
Chris@0 941 // Open tags reset the indent level.
Chris@0 942 if ($tokens[$i]['code'] === T_OPEN_TAG
Chris@0 943 || $tokens[$i]['code'] === T_OPEN_TAG_WITH_ECHO
Chris@0 944 ) {
Chris@0 945 if ($this->_debug === true) {
Chris@0 946 $line = $tokens[$i]['line'];
Chris@0 947 echo "Open PHP tag found on line $line".PHP_EOL;
Chris@0 948 }
Chris@0 949
Chris@0 950 if ($checkToken === null) {
Chris@0 951 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true);
Chris@0 952 $currentIndent = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])));
Chris@0 953 } else {
Chris@0 954 $currentIndent = ($tokens[$i]['column'] - 1);
Chris@0 955 }
Chris@0 956
Chris@0 957 $lastOpenTag = $i;
Chris@0 958
Chris@0 959 if (isset($adjustments[$i]) === true) {
Chris@0 960 $currentIndent += $adjustments[$i];
Chris@0 961 }
Chris@0 962
Chris@0 963 // Make sure it is divisible by our expected indent.
Chris@0 964 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 965 $setIndents[$i] = $currentIndent;
Chris@0 966
Chris@0 967 if ($this->_debug === true) {
Chris@0 968 $type = $tokens[$i]['type'];
Chris@0 969 echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
Chris@0 970 }
Chris@0 971
Chris@0 972 continue;
Chris@0 973 }//end if
Chris@0 974
Chris@0 975 // Close tags reset the indent level, unless they are closing a tag
Chris@0 976 // opened on the same line.
Chris@0 977 if ($tokens[$i]['code'] === T_CLOSE_TAG) {
Chris@0 978 if ($this->_debug === true) {
Chris@0 979 $line = $tokens[$i]['line'];
Chris@0 980 echo "Close PHP tag found on line $line".PHP_EOL;
Chris@0 981 }
Chris@0 982
Chris@0 983 if ($tokens[$lastOpenTag]['line'] !== $tokens[$i]['line']) {
Chris@0 984 $currentIndent = ($tokens[$i]['column'] - 1);
Chris@0 985 $lastCloseTag = $i;
Chris@0 986 } else {
Chris@0 987 if ($lastCloseTag === null) {
Chris@0 988 $currentIndent = 0;
Chris@0 989 } else {
Chris@0 990 $currentIndent = ($tokens[$lastCloseTag]['column'] - 1);
Chris@0 991 }
Chris@0 992 }
Chris@0 993
Chris@0 994 if (isset($adjustments[$i]) === true) {
Chris@0 995 $currentIndent += $adjustments[$i];
Chris@0 996 }
Chris@0 997
Chris@0 998 // Make sure it is divisible by our expected indent.
Chris@0 999 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 1000 $setIndents[$i] = $currentIndent;
Chris@0 1001
Chris@0 1002 if ($this->_debug === true) {
Chris@0 1003 $type = $tokens[$i]['type'];
Chris@0 1004 echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
Chris@0 1005 }
Chris@0 1006
Chris@0 1007 continue;
Chris@0 1008 }//end if
Chris@0 1009
Chris@0 1010 // Anon classes and functions set the indent based on their own indent level.
Chris@0 1011 if ($tokens[$i]['code'] === T_CLOSURE || $tokens[$i]['code'] === T_ANON_CLASS) {
Chris@0 1012 $closer = $tokens[$i]['scope_closer'];
Chris@0 1013 if ($tokens[$i]['line'] === $tokens[$closer]['line']) {
Chris@0 1014 if ($this->_debug === true) {
Chris@0 1015 $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2)));
Chris@0 1016 $line = $tokens[$i]['line'];
Chris@0 1017 echo "* ignoring single-line $type on line $line".PHP_EOL;
Chris@0 1018 }
Chris@0 1019
Chris@0 1020 $i = $closer;
Chris@0 1021 continue;
Chris@0 1022 }
Chris@0 1023
Chris@0 1024 if ($this->_debug === true) {
Chris@0 1025 $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2)));
Chris@0 1026 $line = $tokens[$i]['line'];
Chris@0 1027 echo "Open $type on line $line".PHP_EOL;
Chris@0 1028 }
Chris@0 1029
Chris@0 1030 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true);
Chris@0 1031 $currentIndent = (($tokens[$first]['column'] - 1) + $this->indent);
Chris@0 1032
Chris@0 1033 if (isset($adjustments[$first]) === true) {
Chris@0 1034 $currentIndent += $adjustments[$first];
Chris@0 1035 }
Chris@0 1036
Chris@0 1037 // Make sure it is divisible by our expected indent.
Chris@0 1038 $currentIndent = (int) (floor($currentIndent / $this->indent) * $this->indent);
Chris@0 1039 $i = $tokens[$i]['scope_opener'];
Chris@0 1040 $setIndents[$i] = $currentIndent;
Chris@0 1041
Chris@0 1042 if ($this->_debug === true) {
Chris@0 1043 $type = $tokens[$i]['type'];
Chris@0 1044 echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
Chris@0 1045 }
Chris@0 1046
Chris@0 1047 continue;
Chris@0 1048 }//end if
Chris@0 1049
Chris@0 1050 // Scope openers increase the indent level.
Chris@0 1051 if (isset($tokens[$i]['scope_condition']) === true
Chris@0 1052 && isset($tokens[$i]['scope_opener']) === true
Chris@0 1053 && $tokens[$i]['scope_opener'] === $i
Chris@0 1054 ) {
Chris@0 1055 $closer = $tokens[$i]['scope_closer'];
Chris@0 1056 if ($tokens[$i]['line'] === $tokens[$closer]['line']) {
Chris@0 1057 if ($this->_debug === true) {
Chris@0 1058 $line = $tokens[$i]['line'];
Chris@0 1059 $type = $tokens[$i]['type'];
Chris@0 1060 echo "* ignoring single-line $type on line $line".PHP_EOL;
Chris@0 1061 }
Chris@0 1062
Chris@0 1063 $i = $closer;
Chris@0 1064 continue;
Chris@0 1065 }
Chris@0 1066
Chris@0 1067 $condition = $tokens[$tokens[$i]['scope_condition']]['code'];
Chris@0 1068 if (isset(PHP_CodeSniffer_Tokens::$scopeOpeners[$condition]) === true
Chris@0 1069 && in_array($condition, $this->nonIndentingScopes) === false
Chris@0 1070 ) {
Chris@0 1071 if ($this->_debug === true) {
Chris@0 1072 $line = $tokens[$i]['line'];
Chris@0 1073 $type = $tokens[$tokens[$i]['scope_condition']]['type'];
Chris@0 1074 echo "Open scope ($type) on line $line".PHP_EOL;
Chris@0 1075 }
Chris@0 1076
Chris@0 1077 $currentIndent += $this->indent;
Chris@0 1078 $setIndents[$i] = $currentIndent;
Chris@0 1079 $openScopes[$tokens[$i]['scope_closer']] = $tokens[$i]['scope_condition'];
Chris@0 1080
Chris@0 1081 if ($this->_debug === true) {
Chris@0 1082 $type = $tokens[$i]['type'];
Chris@0 1083 echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
Chris@0 1084 }
Chris@0 1085
Chris@0 1086 continue;
Chris@0 1087 }
Chris@0 1088 }//end if
Chris@0 1089
Chris@0 1090 // JS objects set the indent level.
Chris@0 1091 if ($phpcsFile->tokenizerType === 'JS'
Chris@0 1092 && $tokens[$i]['code'] === T_OBJECT
Chris@0 1093 ) {
Chris@0 1094 $closer = $tokens[$i]['bracket_closer'];
Chris@0 1095 if ($tokens[$i]['line'] === $tokens[$closer]['line']) {
Chris@0 1096 if ($this->_debug === true) {
Chris@0 1097 $line = $tokens[$i]['line'];
Chris@0 1098 echo "* ignoring single-line JS object on line $line".PHP_EOL;
Chris@0 1099 }
Chris@0 1100
Chris@0 1101 $i = $closer;
Chris@0 1102 continue;
Chris@0 1103 }
Chris@0 1104
Chris@0 1105 if ($this->_debug === true) {
Chris@0 1106 $line = $tokens[$i]['line'];
Chris@0 1107 echo "Open JS object on line $line".PHP_EOL;
Chris@0 1108 }
Chris@0 1109
Chris@0 1110 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true);
Chris@0 1111 $currentIndent = (($tokens[$first]['column'] - 1) + $this->indent);
Chris@0 1112 if (isset($adjustments[$first]) === true) {
Chris@0 1113 $currentIndent += $adjustments[$first];
Chris@0 1114 }
Chris@0 1115
Chris@0 1116 // Make sure it is divisible by our expected indent.
Chris@0 1117 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 1118 $setIndents[$first] = $currentIndent;
Chris@0 1119
Chris@0 1120 if ($this->_debug === true) {
Chris@0 1121 $type = $tokens[$first]['type'];
Chris@0 1122 echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL;
Chris@0 1123 }
Chris@0 1124
Chris@0 1125 continue;
Chris@0 1126 }//end if
Chris@0 1127
Chris@0 1128 // Closing an anon class or function.
Chris@0 1129 if (isset($tokens[$i]['scope_condition']) === true
Chris@0 1130 && $tokens[$i]['scope_closer'] === $i
Chris@0 1131 && ($tokens[$tokens[$i]['scope_condition']]['code'] === T_CLOSURE
Chris@0 1132 || $tokens[$tokens[$i]['scope_condition']]['code'] === T_ANON_CLASS)
Chris@0 1133 ) {
Chris@0 1134 if ($this->_debug === true) {
Chris@0 1135 $type = str_replace('_', ' ', strtolower(substr($tokens[$tokens[$i]['scope_condition']]['type'], 2)));
Chris@0 1136 $line = $tokens[$i]['line'];
Chris@0 1137 echo "Close $type on line $line".PHP_EOL;
Chris@0 1138 }
Chris@0 1139
Chris@0 1140 $prev = false;
Chris@0 1141
Chris@0 1142 $object = 0;
Chris@0 1143 if ($phpcsFile->tokenizerType === 'JS') {
Chris@0 1144 $conditions = $tokens[$i]['conditions'];
Chris@0 1145 krsort($conditions, SORT_NUMERIC);
Chris@0 1146 foreach ($conditions as $token => $condition) {
Chris@0 1147 if ($condition === T_OBJECT) {
Chris@0 1148 $object = $token;
Chris@0 1149 break;
Chris@0 1150 }
Chris@0 1151 }
Chris@0 1152
Chris@0 1153 if ($this->_debug === true && $object !== 0) {
Chris@0 1154 $line = $tokens[$object]['line'];
Chris@0 1155 echo "\t* token is inside JS object $object on line $line *".PHP_EOL;
Chris@0 1156 }
Chris@0 1157 }
Chris@0 1158
Chris@0 1159 $parens = 0;
Chris@0 1160 if (isset($tokens[$i]['nested_parenthesis']) === true
Chris@0 1161 && empty($tokens[$i]['nested_parenthesis']) === false
Chris@0 1162 ) {
Chris@0 1163 end($tokens[$i]['nested_parenthesis']);
Chris@0 1164 $parens = key($tokens[$i]['nested_parenthesis']);
Chris@0 1165 if ($this->_debug === true) {
Chris@0 1166 $line = $tokens[$parens]['line'];
Chris@0 1167 echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL;
Chris@0 1168 }
Chris@0 1169 }
Chris@0 1170
Chris@0 1171 $condition = 0;
Chris@0 1172 if (isset($tokens[$i]['conditions']) === true
Chris@0 1173 && empty($tokens[$i]['conditions']) === false
Chris@0 1174 ) {
Chris@0 1175 end($tokens[$i]['conditions']);
Chris@0 1176 $condition = key($tokens[$i]['conditions']);
Chris@0 1177 if ($this->_debug === true) {
Chris@0 1178 $line = $tokens[$condition]['line'];
Chris@0 1179 $type = $tokens[$condition]['type'];
Chris@0 1180 echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL;
Chris@0 1181 }
Chris@0 1182 }
Chris@0 1183
Chris@0 1184 if ($parens > $object && $parens > $condition) {
Chris@0 1185 if ($this->_debug === true) {
Chris@0 1186 echo "\t* using parenthesis *".PHP_EOL;
Chris@0 1187 }
Chris@0 1188
Chris@0 1189 $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($parens - 1), null, true);
Chris@0 1190 $object = 0;
Chris@0 1191 $condition = 0;
Chris@0 1192 } else if ($object > 0 && $object >= $condition) {
Chris@0 1193 if ($this->_debug === true) {
Chris@0 1194 echo "\t* using object *".PHP_EOL;
Chris@0 1195 }
Chris@0 1196
Chris@0 1197 $prev = $object;
Chris@0 1198 $parens = 0;
Chris@0 1199 $condition = 0;
Chris@0 1200 } else if ($condition > 0) {
Chris@0 1201 if ($this->_debug === true) {
Chris@0 1202 echo "\t* using condition *".PHP_EOL;
Chris@0 1203 }
Chris@0 1204
Chris@0 1205 $prev = $condition;
Chris@0 1206 $object = 0;
Chris@0 1207 $parens = 0;
Chris@0 1208 }//end if
Chris@0 1209
Chris@0 1210 if ($prev === false) {
Chris@0 1211 $prev = $phpcsFile->findPrevious(array(T_EQUAL, T_RETURN), ($tokens[$i]['scope_condition'] - 1), null, false, null, true);
Chris@0 1212 if ($prev === false) {
Chris@0 1213 $prev = $i;
Chris@0 1214 if ($this->_debug === true) {
Chris@0 1215 echo "\t* could not find a previous T_EQUAL or T_RETURN token; will use current token *".PHP_EOL;
Chris@0 1216 }
Chris@0 1217 }
Chris@0 1218 }
Chris@0 1219
Chris@0 1220 if ($this->_debug === true) {
Chris@0 1221 $line = $tokens[$prev]['line'];
Chris@0 1222 $type = $tokens[$prev]['type'];
Chris@0 1223 echo "\t* previous token is $type on line $line *".PHP_EOL;
Chris@0 1224 }
Chris@0 1225
Chris@0 1226 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
Chris@0 1227 if ($this->_debug === true) {
Chris@0 1228 $line = $tokens[$first]['line'];
Chris@0 1229 $type = $tokens[$first]['type'];
Chris@0 1230 echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
Chris@0 1231 }
Chris@0 1232
Chris@0 1233 $prev = $phpcsFile->findStartOfStatement($first);
Chris@0 1234 if ($prev !== $first) {
Chris@0 1235 // This is not the start of the statement.
Chris@0 1236 if ($this->_debug === true) {
Chris@0 1237 $line = $tokens[$prev]['line'];
Chris@0 1238 $type = $tokens[$prev]['type'];
Chris@0 1239 echo "\t* amended previous is $type on line $line *".PHP_EOL;
Chris@0 1240 }
Chris@0 1241
Chris@0 1242 $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
Chris@0 1243 if ($this->_debug === true) {
Chris@0 1244 $line = $tokens[$first]['line'];
Chris@0 1245 $type = $tokens[$first]['type'];
Chris@0 1246 echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
Chris@0 1247 }
Chris@0 1248 }
Chris@0 1249
Chris@0 1250 $currentIndent = ($tokens[$first]['column'] - 1);
Chris@0 1251 if ($object > 0 || $condition > 0) {
Chris@0 1252 $currentIndent += $this->indent;
Chris@0 1253 }
Chris@0 1254
Chris@0 1255 if (isset($tokens[$first]['scope_closer']) === true
Chris@0 1256 && $tokens[$first]['scope_closer'] === $first
Chris@0 1257 ) {
Chris@0 1258 if ($this->_debug === true) {
Chris@0 1259 echo "\t* first token is a scope closer *".PHP_EOL;
Chris@0 1260 }
Chris@0 1261
Chris@0 1262 if ($condition === 0 || $tokens[$condition]['scope_opener'] < $first) {
Chris@0 1263 $currentIndent = $setIndents[$first];
Chris@0 1264 } else if ($this->_debug === true) {
Chris@0 1265 echo "\t* ignoring scope closer *".PHP_EOL;
Chris@0 1266 }
Chris@0 1267 }
Chris@0 1268
Chris@0 1269 // Make sure it is divisible by our expected indent.
Chris@0 1270 $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
Chris@0 1271 $setIndents[$first] = $currentIndent;
Chris@0 1272
Chris@0 1273 if ($this->_debug === true) {
Chris@0 1274 $type = $tokens[$first]['type'];
Chris@0 1275 echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL;
Chris@0 1276 }
Chris@0 1277 }//end if
Chris@0 1278 }//end for
Chris@0 1279
Chris@0 1280 // Don't process the rest of the file.
Chris@0 1281 return $phpcsFile->numTokens;
Chris@0 1282
Chris@0 1283 }//end process()
Chris@0 1284
Chris@0 1285
Chris@0 1286 }//end class