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