Completed
Branch development (b1b115)
by Johannes
10:28
created

ScopeIndentSniff   F

Complexity

Total Complexity 286

Size/Duplication

Total Lines 1290
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 286
c 0
b 0
f 0
dl 0
loc 1290
rs 0.8

How to fix   Complexity   

Complex Class

Complex classes like ScopeIndentSniff often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ScopeIndentSniff, and based on these observations, apply Extract Interface, too.

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