• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 14454424553

14 Apr 2025 07:49PM UTC coverage: 77.579% (+2.3%) from 75.291%
14454424553

push

github

web-flow
Merge pull request #983 from PHPCSStandards/phpcs-4.0/feature/sq-2448-remove-support-js-css

Remove CSS/JS support (HUGE PR, reviews welcome!)

119 of 126 new or added lines in 14 files covered. (94.44%)

26 existing lines in 10 files now uncovered.

19384 of 24986 relevant lines covered (77.58%)

78.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.41
/src/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php
1
<?php
2
/**
3
 * Checks that control structures are defined and indented correctly.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Standards\Generic\Sniffs\WhiteSpace;
11

12
use PHP_CodeSniffer\Config;
13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\Sniff;
15
use PHP_CodeSniffer\Util\Tokens;
16

17
class ScopeIndentSniff implements Sniff
18
{
19

20
    /**
21
     * The number of spaces code should be indented.
22
     *
23
     * @var integer
24
     */
25
    public $indent = 4;
26

27
    /**
28
     * Does the indent need to be exactly right?
29
     *
30
     * If TRUE, indent needs to be exactly $indent spaces. If FALSE,
31
     * indent needs to be at least $indent spaces (but can be more).
32
     *
33
     * @var boolean
34
     */
35
    public $exact = false;
36

37
    /**
38
     * Should tabs be used for indenting?
39
     *
40
     * If TRUE, fixes will be made using tabs instead of spaces.
41
     * The size of each tab is important, so it should be specified
42
     * using the --tab-width CLI argument.
43
     *
44
     * @var boolean
45
     */
46
    public $tabIndent = false;
47

48
    /**
49
     * The --tab-width CLI value that is being used.
50
     *
51
     * @var integer
52
     */
53
    private $tabWidth = null;
54

55
    /**
56
     * List of tokens not needing to be checked for indentation.
57
     *
58
     * Useful to allow Sniffs based on this to easily ignore/skip some
59
     * tokens from verification. For example, inline HTML sections
60
     * or PHP open/close tags can escape from here and have their own
61
     * rules elsewhere.
62
     *
63
     * @var int[]
64
     */
65
    public $ignoreIndentationTokens = [];
66

67
    /**
68
     * List of tokens not needing to be checked for indentation.
69
     *
70
     * This is a cached copy of the public version of this var, which
71
     * can be set in a ruleset file, and some core ignored tokens.
72
     *
73
     * @var array<int|string, bool>
74
     */
75
    private $ignoreIndentation = [];
76

77
    /**
78
     * Any scope openers that should not cause an indent.
79
     *
80
     * @var int[]
81
     */
82
    protected $nonIndentingScopes = [];
83

84
    /**
85
     * Show debug output for this sniff.
86
     *
87
     * @var boolean
88
     */
89
    private $debug = false;
90

91

92
    /**
93
     * Returns an array of tokens this test wants to listen for.
94
     *
95
     * @return array<int|string>
96
     */
97
    public function register()
3✔
98
    {
99
        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
3✔
100
            $this->debug = false;
3✔
101
        }
102

103
        return [T_OPEN_TAG];
3✔
104

105
    }//end register()
106

107

108
    /**
109
     * Processes this test, when one of its tokens is encountered.
110
     *
111
     * @param \PHP_CodeSniffer\Files\File $phpcsFile All the tokens found in the document.
112
     * @param int                         $stackPtr  The position of the current token
113
     *                                               in the stack passed in $tokens.
114
     *
115
     * @return int
116
     */
117
    public function process(File $phpcsFile, $stackPtr)
3✔
118
    {
119
        $debug = Config::getConfigData('scope_indent_debug');
3✔
120
        if ($debug !== null) {
3✔
121
            $this->debug = (bool) $debug;
×
122
        }
123

124
        if ($this->tabWidth === null) {
3✔
125
            if (isset($phpcsFile->config->tabWidth) === false || $phpcsFile->config->tabWidth === 0) {
3✔
126
                // We have no idea how wide tabs are, so assume 4 spaces for fixing.
127
                // It shouldn't really matter because indent checks elsewhere in the
128
                // standard should fix things up.
129
                $this->tabWidth = 4;
3✔
130
            } else {
131
                $this->tabWidth = $phpcsFile->config->tabWidth;
3✔
132
            }
133
        }
134

135
        $lastOpenTag       = $stackPtr;
3✔
136
        $lastCloseTag      = null;
3✔
137
        $openScopes        = [];
3✔
138
        $adjustments       = [];
3✔
139
        $setIndents        = [];
3✔
140
        $disableExactStack = [];
3✔
141
        $disableExactEnd   = 0;
3✔
142

143
        $tokens  = $phpcsFile->getTokens();
3✔
144
        $first   = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr);
3✔
145
        $trimmed = ltrim($tokens[$first]['content']);
3✔
146
        if ($trimmed === '') {
3✔
147
            $currentIndent = ($tokens[$stackPtr]['column'] - 1);
3✔
148
        } else {
149
            $currentIndent = (strlen($tokens[$first]['content']) - strlen($trimmed));
3✔
150
        }
151

152
        if ($this->debug === true) {
3✔
153
            $line = $tokens[$stackPtr]['line'];
×
154
            echo PHP_EOL."Start with token $stackPtr on line $line with indent $currentIndent".PHP_EOL;
×
155
        }
156

157
        if (empty($this->ignoreIndentation) === true) {
3✔
158
            $this->ignoreIndentation = [T_INLINE_HTML => true];
3✔
159
            foreach ($this->ignoreIndentationTokens as $token) {
3✔
160
                if (is_int($token) === false) {
×
161
                    if (defined($token) === false) {
×
162
                        continue;
×
163
                    }
164

165
                    $token = constant($token);
×
166
                }
167

168
                $this->ignoreIndentation[$token] = true;
×
169
            }
170
        }//end if
171

172
        $this->exact     = (bool) $this->exact;
3✔
173
        $this->tabIndent = (bool) $this->tabIndent;
3✔
174

175
        $checkAnnotations = $phpcsFile->config->annotations;
3✔
176

177
        for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) {
3✔
178
            if ($i === false) {
3✔
179
                // Something has gone very wrong; maybe a parse error.
180
                break;
×
181
            }
182

183
            if ($checkAnnotations === true
3✔
184
                && $tokens[$i]['code'] === T_PHPCS_SET
3✔
185
                && isset($tokens[$i]['sniffCode']) === true
3✔
186
                && $tokens[$i]['sniffCode'] === 'Generic.WhiteSpace.ScopeIndent'
3✔
187
                && $tokens[$i]['sniffProperty'] === 'exact'
3✔
188
            ) {
189
                $value = $tokens[$i]['sniffPropertyValue'];
3✔
190
                if ($value === 'true') {
3✔
191
                    $value = true;
3✔
192
                } else if ($value === 'false') {
3✔
193
                    $value = false;
3✔
194
                } else {
195
                    $value = (bool) $value;
×
196
                }
197

198
                $this->exact = $value;
3✔
199

200
                if ($this->debug === true) {
3✔
201
                    $line = $tokens[$i]['line'];
×
202
                    if ($this->exact === true) {
×
203
                        $value = 'true';
×
204
                    } else {
205
                        $value = 'false';
×
206
                    }
207

208
                    echo "* token $i on line $line set exact flag to $value *".PHP_EOL;
×
209
                }
210
            }//end if
211

212
            $checkToken  = null;
3✔
213
            $checkIndent = null;
3✔
214

215
            /*
216
                Don't check indents exactly between parenthesis or arrays as they
217
                tend to have custom rules, such as with multi-line function calls
218
                and control structure conditions.
219
            */
220

221
            $exact = $this->exact;
3✔
222

223
            if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS
3✔
224
                && isset($tokens[$i]['parenthesis_closer']) === true
3✔
225
            ) {
226
                $disableExactStack[$tokens[$i]['parenthesis_closer']] = $tokens[$i]['parenthesis_closer'];
3✔
227
                $disableExactEnd = max($disableExactEnd, $tokens[$i]['parenthesis_closer']);
3✔
228
                if ($this->debug === true) {
3✔
229
                    $line = $tokens[$i]['line'];
×
230
                    $type = $tokens[$disableExactEnd]['type'];
×
231
                    echo "Opening parenthesis found on line $line".PHP_EOL;
×
232
                    echo "\t=> disabling exact indent checking until $disableExactEnd ($type)".PHP_EOL;
×
233
                }
234
            }
235

236
            if ($exact === true && $i < $disableExactEnd) {
3✔
237
                $exact = false;
3✔
238
            }
239

240
            // Detect line changes and figure out where the indent is.
241
            if ($tokens[$i]['column'] === 1) {
3✔
242
                $trimmed = ltrim($tokens[$i]['content']);
3✔
243
                if ($trimmed === '') {
3✔
244
                    if (isset($tokens[($i + 1)]) === true
3✔
245
                        && $tokens[$i]['line'] === $tokens[($i + 1)]['line']
3✔
246
                    ) {
247
                        $checkToken  = ($i + 1);
3✔
248
                        $tokenIndent = ($tokens[($i + 1)]['column'] - 1);
3✔
249
                    }
250
                } else {
251
                    $checkToken  = $i;
3✔
252
                    $tokenIndent = (strlen($tokens[$i]['content']) - strlen($trimmed));
3✔
253
                }
254
            }
255

256
            // Closing parenthesis should just be indented to at least
257
            // the same level as where they were opened (but can be more).
258
            if (($checkToken !== null
3✔
259
                && $tokens[$checkToken]['code'] === T_CLOSE_PARENTHESIS
3✔
260
                && isset($tokens[$checkToken]['parenthesis_opener']) === true)
3✔
261
                || ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS
3✔
262
                && isset($tokens[$i]['parenthesis_opener']) === true)
3✔
263
            ) {
264
                if ($checkToken !== null) {
3✔
265
                    $parenCloser = $checkToken;
3✔
266
                } else {
267
                    $parenCloser = $i;
3✔
268
                }
269

270
                if ($this->debug === true) {
3✔
271
                    $line = $tokens[$i]['line'];
×
272
                    echo "Closing parenthesis found on line $line".PHP_EOL;
×
273
                }
274

275
                $parenOpener = $tokens[$parenCloser]['parenthesis_opener'];
3✔
276
                if ($tokens[$parenCloser]['line'] !== $tokens[$parenOpener]['line']) {
3✔
277
                    $parens = 0;
3✔
278
                    if (isset($tokens[$parenCloser]['nested_parenthesis']) === true
3✔
279
                        && empty($tokens[$parenCloser]['nested_parenthesis']) === false
3✔
280
                    ) {
281
                        $parens = $tokens[$parenCloser]['nested_parenthesis'];
3✔
282
                        end($parens);
3✔
283
                        $parens = key($parens);
3✔
284
                        if ($this->debug === true) {
3✔
285
                            $line = $tokens[$parens]['line'];
×
286
                            echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL;
×
287
                        }
288
                    }
289

290
                    $condition = 0;
3✔
291
                    if (isset($tokens[$parenCloser]['conditions']) === true
3✔
292
                        && empty($tokens[$parenCloser]['conditions']) === false
3✔
293
                        && (isset($tokens[$parenCloser]['parenthesis_owner']) === false
3✔
294
                        || $parens > 0)
3✔
295
                    ) {
296
                        $condition = $tokens[$parenCloser]['conditions'];
3✔
297
                        end($condition);
3✔
298
                        $condition = key($condition);
3✔
299
                        if ($this->debug === true) {
3✔
300
                            $line = $tokens[$condition]['line'];
×
301
                            $type = $tokens[$condition]['type'];
×
302
                            echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL;
×
303
                        }
304
                    }
305

306
                    if ($parens > $condition) {
3✔
307
                        if ($this->debug === true) {
3✔
308
                            echo "\t* using parenthesis *".PHP_EOL;
×
309
                        }
310

311
                        $parenOpener = $parens;
3✔
312
                        $condition   = 0;
3✔
313
                    } else if ($condition > 0) {
3✔
314
                        if ($this->debug === true) {
3✔
315
                            echo "\t* using condition *".PHP_EOL;
×
316
                        }
317

318
                        $parenOpener = $condition;
3✔
319
                        $parens      = 0;
3✔
320
                    }
321

322
                    $exact = false;
3✔
323

324
                    $lastOpenTagConditions = array_keys($tokens[$lastOpenTag]['conditions']);
3✔
325
                    $lastOpenTagCondition  = array_pop($lastOpenTagConditions);
3✔
326

327
                    if ($condition > 0 && $lastOpenTagCondition === $condition) {
3✔
328
                        if ($this->debug === true) {
3✔
329
                            echo "\t* open tag is inside condition; using open tag *".PHP_EOL;
×
330
                        }
331

332
                        $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $lastOpenTag, true);
3✔
333
                        if ($this->debug === true) {
3✔
334
                            $line = $tokens[$first]['line'];
×
335
                            $type = $tokens[$first]['type'];
×
336
                            echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
×
337
                        }
338

339
                        $checkIndent = ($tokens[$first]['column'] - 1);
3✔
340
                        if (isset($adjustments[$condition]) === true) {
3✔
341
                            $checkIndent += $adjustments[$condition];
×
342
                        }
343

344
                        $currentIndent = $checkIndent;
3✔
345

346
                        if ($this->debug === true) {
3✔
347
                            $type = $tokens[$lastOpenTag]['type'];
×
348
                            echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $lastOpenTag ($type)".PHP_EOL;
2✔
349
                        }
350
                    } else if ($condition > 0
3✔
351
                        && isset($tokens[$condition]['scope_opener']) === true
3✔
352
                        && isset($setIndents[$tokens[$condition]['scope_opener']]) === true
3✔
353
                    ) {
354
                        $checkIndent = $setIndents[$tokens[$condition]['scope_opener']];
3✔
355
                        if (isset($adjustments[$condition]) === true) {
3✔
356
                            $checkIndent += $adjustments[$condition];
×
357
                        }
358

359
                        $currentIndent = $checkIndent;
3✔
360

361
                        if ($this->debug === true) {
3✔
362
                            $type = $tokens[$condition]['type'];
×
363
                            echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $condition ($type)".PHP_EOL;
2✔
364
                        }
365
                    } else {
366
                        $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parenOpener, true);
3✔
367

368
                        $checkIndent = ($tokens[$first]['column'] - 1);
3✔
369
                        if (isset($adjustments[$first]) === true) {
3✔
370
                            $checkIndent += $adjustments[$first];
3✔
371
                        }
372

373
                        if ($this->debug === true) {
3✔
374
                            $line = $tokens[$first]['line'];
×
375
                            $type = $tokens[$first]['type'];
×
376
                            echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
×
377
                        }
378

379
                        if ($first === $tokens[$parenCloser]['parenthesis_opener']
3✔
380
                            && $tokens[($first - 1)]['line'] === $tokens[$first]['line']
3✔
381
                        ) {
382
                            // This is unlikely to be the start of the statement, so look
383
                            // back further to find it.
384
                            $first--;
3✔
385
                            if ($this->debug === true) {
3✔
386
                                $line = $tokens[$first]['line'];
×
387
                                $type = $tokens[$first]['type'];
×
388
                                echo "\t* first token is the parenthesis opener *".PHP_EOL;
×
389
                                echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
×
390
                            }
391
                        }
392

393
                        $prev = $phpcsFile->findStartOfStatement($first, T_COMMA);
3✔
394
                        if ($prev !== $first) {
3✔
395
                            // This is not the start of the statement.
396
                            if ($this->debug === true) {
3✔
397
                                $line = $tokens[$prev]['line'];
×
398
                                $type = $tokens[$prev]['type'];
×
399
                                echo "\t* previous is $type on line $line *".PHP_EOL;
×
400
                            }
401

402
                            $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $prev, true);
3✔
403
                            if ($first !== false) {
3✔
404
                                $prev  = $phpcsFile->findStartOfStatement($first, T_COMMA);
3✔
405
                                $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $prev, true);
3✔
406
                            } else {
407
                                $first = $prev;
3✔
408
                            }
409

410
                            if ($this->debug === true) {
3✔
411
                                $line = $tokens[$first]['line'];
×
412
                                $type = $tokens[$first]['type'];
×
413
                                echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
×
414
                            }
415
                        }//end if
416

417
                        if (isset($tokens[$first]['scope_closer']) === true
3✔
418
                            && $tokens[$first]['scope_closer'] === $first
3✔
419
                        ) {
420
                            if ($this->debug === true) {
×
421
                                echo "\t* first token is a scope closer *".PHP_EOL;
×
422
                            }
423

424
                            if (isset($tokens[$first]['scope_condition']) === true) {
×
425
                                $scopeCloser = $first;
×
426
                                $first       = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true);
×
427

428
                                $currentIndent = ($tokens[$first]['column'] - 1);
×
429
                                if (isset($adjustments[$first]) === true) {
×
430
                                    $currentIndent += $adjustments[$first];
×
431
                                }
432

433
                                // Make sure it is divisible by our expected indent.
434
                                if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) {
×
435
                                    $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent);
×
436
                                }
437

438
                                $setIndents[$first] = $currentIndent;
×
439

440
                                if ($this->debug === true) {
×
441
                                    $type = $tokens[$first]['type'];
×
442
                                    echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL;
×
443
                                }
444
                            }//end if
445
                        } else {
446
                            // Don't force current indent to be divisible because there could be custom
447
                            // rules in place between parenthesis, such as with arrays.
448
                            $currentIndent = ($tokens[$first]['column'] - 1);
3✔
449
                            if (isset($adjustments[$first]) === true) {
3✔
450
                                $currentIndent += $adjustments[$first];
3✔
451
                            }
452

453
                            $setIndents[$first] = $currentIndent;
3✔
454

455
                            if ($this->debug === true) {
3✔
456
                                $type = $tokens[$first]['type'];
×
457
                                echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL;
2✔
458
                            }
459
                        }//end if
460
                    }//end if
461
                } else if ($this->debug === true) {
3✔
462
                    echo "\t * ignoring single-line definition *".PHP_EOL;
×
463
                }//end if
464
            }//end if
465

466
            // Closing short array bracket should just be indented to at least
467
            // the same level as where it was opened (but can be more).
468
            if ($tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
3✔
469
                || ($checkToken !== null
3✔
470
                && $tokens[$checkToken]['code'] === T_CLOSE_SHORT_ARRAY)
3✔
471
            ) {
472
                if ($checkToken !== null) {
3✔
473
                    $arrayCloser = $checkToken;
3✔
474
                } else {
475
                    $arrayCloser = $i;
3✔
476
                }
477

478
                if ($this->debug === true) {
3✔
479
                    $line = $tokens[$arrayCloser]['line'];
×
480
                    echo "Closing short array bracket found on line $line".PHP_EOL;
×
481
                }
482

483
                $arrayOpener = $tokens[$arrayCloser]['bracket_opener'];
3✔
484
                if ($tokens[$arrayCloser]['line'] !== $tokens[$arrayOpener]['line']) {
3✔
485
                    $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $arrayOpener, true);
3✔
486
                    $exact = false;
3✔
487

488
                    if ($this->debug === true) {
3✔
489
                        $line = $tokens[$first]['line'];
×
490
                        $type = $tokens[$first]['type'];
×
491
                        echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
×
492
                    }
493

494
                    if ($first === $tokens[$arrayCloser]['bracket_opener']) {
3✔
495
                        // This is unlikely to be the start of the statement, so look
496
                        // back further to find it.
497
                        $first--;
3✔
498
                    }
499

500
                    $prev = $phpcsFile->findStartOfStatement($first, [T_COMMA, T_DOUBLE_ARROW]);
3✔
501
                    if ($prev !== $first) {
3✔
502
                        // This is not the start of the statement.
503
                        if ($this->debug === true) {
3✔
504
                            $line = $tokens[$prev]['line'];
×
505
                            $type = $tokens[$prev]['type'];
×
506
                            echo "\t* previous is $type on line $line *".PHP_EOL;
×
507
                        }
508

509
                        $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
3✔
510
                        $prev  = $phpcsFile->findStartOfStatement($first, [T_COMMA, T_DOUBLE_ARROW]);
3✔
511
                        $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
3✔
512
                        if ($this->debug === true) {
3✔
513
                            $line = $tokens[$first]['line'];
×
514
                            $type = $tokens[$first]['type'];
×
515
                            echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
2✔
516
                        }
517
                    } else if ($tokens[$first]['code'] === T_WHITESPACE) {
3✔
518
                        $first = $phpcsFile->findNext(T_WHITESPACE, ($first + 1), null, true);
3✔
519
                    }
520

521
                    $checkIndent = ($tokens[$first]['column'] - 1);
3✔
522
                    if (isset($adjustments[$first]) === true) {
3✔
523
                        $checkIndent += $adjustments[$first];
×
524
                    }
525

526
                    if (isset($tokens[$first]['scope_closer']) === true
3✔
527
                        && $tokens[$first]['scope_closer'] === $first
3✔
528
                    ) {
529
                        // The first token is a scope closer and would have already
530
                        // been processed and set the indent level correctly, so
531
                        // don't adjust it again.
532
                        if ($this->debug === true) {
×
533
                            echo "\t* first token is a scope closer; ignoring closing short array bracket *".PHP_EOL;
×
534
                        }
535

536
                        if (isset($setIndents[$first]) === true) {
×
537
                            $currentIndent = $setIndents[$first];
×
538
                            if ($this->debug === true) {
×
539
                                echo "\t=> indent reset to $currentIndent".PHP_EOL;
×
540
                            }
541
                        }
542
                    } else {
543
                        // Don't force current indent to be divisible because there could be custom
544
                        // rules in place for arrays.
545
                        $currentIndent = ($tokens[$first]['column'] - 1);
3✔
546
                        if (isset($adjustments[$first]) === true) {
3✔
547
                            $currentIndent += $adjustments[$first];
×
548
                        }
549

550
                        $setIndents[$first] = $currentIndent;
3✔
551

552
                        if ($this->debug === true) {
3✔
553
                            $type = $tokens[$first]['type'];
×
554
                            echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL;
2✔
555
                        }
556
                    }//end if
557
                } else if ($this->debug === true) {
3✔
558
                    echo "\t * ignoring single-line definition *".PHP_EOL;
×
559
                }//end if
560
            }//end if
561

562
            // Adjust lines within scopes while auto-fixing.
563
            if ($checkToken !== null
3✔
564
                && $exact === false
3✔
565
                && (empty($tokens[$checkToken]['conditions']) === false
3✔
566
                || (isset($tokens[$checkToken]['scope_opener']) === true
3✔
567
                && $tokens[$checkToken]['scope_opener'] === $checkToken))
3✔
568
            ) {
569
                if (empty($tokens[$checkToken]['conditions']) === false) {
3✔
570
                    $condition = $tokens[$checkToken]['conditions'];
3✔
571
                    end($condition);
3✔
572
                    $condition = key($condition);
3✔
573
                } else {
574
                    $condition = $tokens[$checkToken]['scope_condition'];
3✔
575
                }
576

577
                $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true);
3✔
578

579
                if (isset($adjustments[$first]) === true
3✔
580
                    && (($adjustments[$first] < 0 && $tokenIndent > $currentIndent)
3✔
581
                    || ($adjustments[$first] > 0 && $tokenIndent < $currentIndent))
3✔
582
                ) {
583
                    $length = ($tokenIndent + $adjustments[$first]);
3✔
584

585
                    // When fixing, we're going to adjust the indent of this line
586
                    // here automatically, so use this new padding value when
587
                    // comparing the expected padding to the actual padding.
588
                    if ($phpcsFile->fixer->enabled === true) {
3✔
589
                        $tokenIndent = $length;
3✔
590
                        $this->adjustIndent($phpcsFile, $checkToken, $length, $adjustments[$first]);
3✔
591
                    }
592

593
                    if ($this->debug === true) {
3✔
594
                        $line = $tokens[$checkToken]['line'];
×
595
                        $type = $tokens[$checkToken]['type'];
×
596
                        echo "Indent adjusted to $length for $type on line $line".PHP_EOL;
×
597
                    }
598

599
                    $adjustments[$checkToken] = $adjustments[$first];
3✔
600

601
                    if ($this->debug === true) {
3✔
602
                        $line = $tokens[$checkToken]['line'];
×
603
                        $type = $tokens[$checkToken]['type'];
×
604
                        echo "\t=> add adjustment of ".$adjustments[$checkToken]." for token $checkToken ($type) on line $line".PHP_EOL;
×
605
                    }
606
                }//end if
607
            }//end if
608

609
            // Scope closers reset the required indent to the same level as the opening condition.
610
            if (($checkToken !== null
3✔
611
                && (isset($openScopes[$checkToken]) === true
3✔
612
                || (isset($tokens[$checkToken]['scope_condition']) === true
3✔
613
                && isset($tokens[$checkToken]['scope_closer']) === true
3✔
614
                && $tokens[$checkToken]['scope_closer'] === $checkToken
3✔
615
                && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['scope_opener']]['line'])))
3✔
616
                || ($checkToken === null
3✔
617
                && isset($openScopes[$i]) === true)
3✔
618
            ) {
619
                if ($this->debug === true) {
3✔
620
                    if ($checkToken === null) {
×
621
                        $type = $tokens[$tokens[$i]['scope_condition']]['type'];
×
622
                        $line = $tokens[$i]['line'];
×
623
                    } else {
624
                        $type = $tokens[$tokens[$checkToken]['scope_condition']]['type'];
×
625
                        $line = $tokens[$checkToken]['line'];
×
626
                    }
627

628
                    echo "Close scope ($type) on line $line".PHP_EOL;
×
629
                }
630

631
                $scopeCloser = $checkToken;
3✔
632
                if ($scopeCloser === null) {
3✔
633
                    $scopeCloser = $i;
3✔
634
                }
635

636
                $conditionToken = array_pop($openScopes);
3✔
637
                if ($this->debug === true && $conditionToken !== null) {
3✔
638
                    $line = $tokens[$conditionToken]['line'];
×
639
                    $type = $tokens[$conditionToken]['type'];
×
640
                    echo "\t=> removed open scope $conditionToken ($type) on line $line".PHP_EOL;
×
641
                }
642

643
                if (isset($tokens[$scopeCloser]['scope_condition']) === true) {
3✔
644
                    $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $tokens[$scopeCloser]['scope_condition'], true);
3✔
645
                    if ($this->debug === true) {
3✔
646
                        $line = $tokens[$first]['line'];
×
647
                        $type = $tokens[$first]['type'];
×
648
                        echo "\t* first token is $first ($type) on line $line *".PHP_EOL;
×
649
                    }
650

651
                    while ($tokens[$first]['code'] === T_CONSTANT_ENCAPSED_STRING
3✔
652
                        && $tokens[($first - 1)]['code'] === T_CONSTANT_ENCAPSED_STRING
3✔
653
                    ) {
654
                        $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, ($first - 1), true);
3✔
655
                        if ($this->debug === true) {
3✔
656
                            $line = $tokens[$first]['line'];
×
657
                            $type = $tokens[$first]['type'];
×
658
                            echo "\t* found multi-line string; amended first token is $first ($type) on line $line *".PHP_EOL;
×
659
                        }
660
                    }
661

662
                    $currentIndent = ($tokens[$first]['column'] - 1);
3✔
663
                    if (isset($adjustments[$first]) === true) {
3✔
664
                        $currentIndent += $adjustments[$first];
3✔
665
                    }
666

667
                    $setIndents[$scopeCloser] = $currentIndent;
3✔
668

669
                    if ($this->debug === true) {
3✔
670
                        $type = $tokens[$scopeCloser]['type'];
×
671
                        echo "\t=> indent set to $currentIndent by token $scopeCloser ($type)".PHP_EOL;
×
672
                    }
673

674
                    // We only check the indent of scope closers if they are
675
                    // curly braces because other constructs tend to have different rules.
676
                    if ($tokens[$scopeCloser]['code'] === T_CLOSE_CURLY_BRACKET) {
3✔
677
                        $exact = true;
3✔
678
                    } else {
679
                        $checkToken = null;
3✔
680
                    }
681
                }//end if
682
            }//end if
683

684
            if ($checkToken !== null
3✔
685
                && isset(Tokens::$scopeOpeners[$tokens[$checkToken]['code']]) === true
3✔
686
                && in_array($tokens[$checkToken]['code'], $this->nonIndentingScopes, true) === false
3✔
687
                && isset($tokens[$checkToken]['scope_opener']) === true
3✔
688
            ) {
689
                $exact = true;
3✔
690

691
                if ($disableExactEnd > $checkToken) {
3✔
692
                    foreach ($disableExactStack as $disableExactStackEnd) {
3✔
693
                        if ($disableExactStackEnd < $checkToken) {
3✔
694
                            continue;
3✔
695
                        }
696

697
                        if ($tokens[$checkToken]['conditions'] === $tokens[$disableExactStackEnd]['conditions']) {
3✔
698
                            $exact = false;
3✔
699
                            break;
3✔
700
                        }
701
                    }
702
                }
703

704
                $lastOpener = null;
3✔
705
                if (empty($openScopes) === false) {
3✔
706
                    end($openScopes);
3✔
707
                    $lastOpener = current($openScopes);
3✔
708
                }
709

710
                // A scope opener that shares a closer with another token (like multiple
711
                // CASEs using the same BREAK) needs to reduce the indent level so its
712
                // indent is checked correctly. It will then increase the indent again
713
                // (as all openers do) after being checked.
714
                if ($lastOpener !== null
3✔
715
                    && isset($tokens[$lastOpener]['scope_closer']) === true
3✔
716
                    && $tokens[$lastOpener]['level'] === $tokens[$checkToken]['level']
3✔
717
                    && $tokens[$lastOpener]['scope_closer'] === $tokens[$checkToken]['scope_closer']
3✔
718
                ) {
719
                    $currentIndent          -= $this->indent;
3✔
720
                    $setIndents[$lastOpener] = $currentIndent;
3✔
721
                    if ($this->debug === true) {
3✔
722
                        $line = $tokens[$i]['line'];
×
723
                        $type = $tokens[$lastOpener]['type'];
×
724
                        echo "Shared closer found on line $line".PHP_EOL;
×
725
                        echo "\t=> indent set to $currentIndent by token $lastOpener ($type)".PHP_EOL;
×
726
                    }
727
                }
728

729
                if ($tokens[$checkToken]['code'] === T_CLOSURE
3✔
730
                    && $tokenIndent > $currentIndent
3✔
731
                ) {
732
                    // The opener is indented more than needed, which is fine.
733
                    // But just check that it is divisible by our expected indent.
734
                    $checkIndent = (int) (ceil($tokenIndent / $this->indent) * $this->indent);
3✔
735
                    $exact       = false;
3✔
736

737
                    if ($this->debug === true) {
3✔
738
                        $line = $tokens[$i]['line'];
×
739
                        echo "Closure found on line $line".PHP_EOL;
×
740
                        echo "\t=> checking indent of $checkIndent; main indent remains at $currentIndent".PHP_EOL;
×
741
                    }
742
                }
743
            }//end if
744

745
            // Method prefix indentation has to be exact or else it will break
746
            // the rest of the function declaration, and potentially future ones.
747
            if ($checkToken !== null
3✔
748
                && isset(Tokens::$methodPrefixes[$tokens[$checkToken]['code']]) === true
3✔
749
                && $tokens[($checkToken + 1)]['code'] !== T_DOUBLE_COLON
3✔
750
            ) {
751
                $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($checkToken + 1), null, true);
3✔
752
                if ($next === false
3✔
753
                    || ($tokens[$next]['code'] !== T_CLOSURE
3✔
754
                    && $tokens[$next]['code'] !== T_VARIABLE
3✔
755
                    && $tokens[$next]['code'] !== T_FN)
3✔
756
                ) {
757
                    $isMethodPrefix = true;
3✔
758
                    if (isset($tokens[$checkToken]['nested_parenthesis']) === true) {
3✔
759
                        $parenthesis = array_keys($tokens[$checkToken]['nested_parenthesis']);
3✔
760
                        $deepestOpen = array_pop($parenthesis);
3✔
761
                        if (isset($tokens[$deepestOpen]['parenthesis_owner']) === true
3✔
762
                            && $tokens[$tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION
3✔
763
                        ) {
764
                            // This is constructor property promotion and not a method prefix.
765
                            $isMethodPrefix = false;
3✔
766
                        }
767
                    }
768

769
                    if ($isMethodPrefix === true) {
3✔
770
                        if ($this->debug === true) {
3✔
771
                            $line = $tokens[$checkToken]['line'];
×
772
                            $type = $tokens[$checkToken]['type'];
×
773
                            echo "\t* method prefix ($type) found on line $line; indent set to exact *".PHP_EOL;
×
774
                        }
775

776
                        $exact = true;
3✔
777
                    }
778
                }//end if
779
            }//end if
780

781
            // Open PHP tags needs to be indented to exact column positions
782
            // so they don't cause problems with indent checks for the code
783
            // within them, but they don't need to line up with the current indent
784
            // in most cases.
785
            if ($checkToken !== null
3✔
786
                && ($tokens[$checkToken]['code'] === T_OPEN_TAG
3✔
787
                || $tokens[$checkToken]['code'] === T_OPEN_TAG_WITH_ECHO)
3✔
788
            ) {
789
                $checkIndent = ($tokens[$checkToken]['column'] - 1);
3✔
790

791
                // If we are re-opening a block that was closed in the same
792
                // scope as us, then reset the indent back to what the scope opener
793
                // set instead of using whatever indent this open tag has set.
794
                if (empty($tokens[$checkToken]['conditions']) === false) {
3✔
795
                    $close = $phpcsFile->findPrevious(T_CLOSE_TAG, ($checkToken - 1));
3✔
796
                    if ($close !== false
3✔
797
                        && $tokens[$checkToken]['conditions'] === $tokens[$close]['conditions']
3✔
798
                    ) {
799
                        $conditions    = array_keys($tokens[$checkToken]['conditions']);
3✔
800
                        $lastCondition = array_pop($conditions);
3✔
801
                        $lastOpener    = $tokens[$lastCondition]['scope_opener'];
3✔
802
                        $lastCloser    = $tokens[$lastCondition]['scope_closer'];
3✔
803
                        if ($tokens[$lastCloser]['line'] !== $tokens[$checkToken]['line']
3✔
804
                            && isset($setIndents[$lastOpener]) === true
3✔
805
                        ) {
806
                            $checkIndent = $setIndents[$lastOpener];
3✔
807
                        }
808
                    }
809
                }
810
            }//end if
811

812
            // Close tags needs to be indented to exact column positions.
813
            if ($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_TAG) {
3✔
814
                $exact       = true;
3✔
815
                $checkIndent = $currentIndent;
3✔
816
                $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent);
3✔
817
            }
818

819
            // Special case for ELSE statements that are not on the same
820
            // line as the previous IF statements closing brace. They still need
821
            // to have the same indent or it will break code after the block.
822
            if ($checkToken !== null && $tokens[$checkToken]['code'] === T_ELSE) {
3✔
823
                $exact = true;
3✔
824
            }
825

826
            // Don't perform strict checking on chained method calls since they
827
            // are often covered by custom rules.
828
            if ($checkToken !== null
3✔
829
                && ($tokens[$checkToken]['code'] === T_OBJECT_OPERATOR
3✔
830
                || $tokens[$checkToken]['code'] === T_NULLSAFE_OBJECT_OPERATOR)
3✔
831
                && $exact === true
3✔
832
            ) {
833
                $exact = false;
3✔
834
            }
835

836
            if ($checkIndent === null) {
3✔
837
                $checkIndent = $currentIndent;
3✔
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 lines
848
            */
849

850
            if ($checkToken !== null
3✔
851
                && isset($this->ignoreIndentation[$tokens[$checkToken]['code']]) === false
3✔
852
                && (($tokenIndent !== $checkIndent && $exact === true)
3✔
853
                || ($tokenIndent < $checkIndent && $exact === false))
3✔
854
            ) {
855
                $type  = 'IncorrectExact';
3✔
856
                $error = 'Line indented incorrectly; expected ';
3✔
857
                if ($exact === false) {
3✔
858
                    $error .= 'at least ';
3✔
859
                    $type   = 'Incorrect';
3✔
860
                }
861

862
                if ($this->tabIndent === true) {
3✔
863
                    $expectedTabs = floor($checkIndent / $this->tabWidth);
3✔
864
                    $foundTabs    = floor($tokenIndent / $this->tabWidth);
3✔
865
                    $foundSpaces  = ($tokenIndent - ($foundTabs * $this->tabWidth));
3✔
866
                    if ($foundSpaces > 0) {
3✔
867
                        if ($foundTabs > 0) {
3✔
868
                            $error .= '%s tabs, found %s tabs and %s spaces';
3✔
869
                            $data   = [
2✔
870
                                $expectedTabs,
3✔
871
                                $foundTabs,
3✔
872
                                $foundSpaces,
3✔
873
                            ];
2✔
874
                        } else {
875
                            $error .= '%s tabs, found %s spaces';
3✔
876
                            $data   = [
2✔
877
                                $expectedTabs,
3✔
878
                                $foundSpaces,
3✔
879
                            ];
2✔
880
                        }
881
                    } else {
882
                        $error .= '%s tabs, found %s';
3✔
883
                        $data   = [
2✔
884
                            $expectedTabs,
3✔
885
                            $foundTabs,
3✔
886
                        ];
2✔
887
                    }//end if
888
                } else {
889
                    $error .= '%s spaces, found %s';
3✔
890
                    $data   = [
2✔
891
                        $checkIndent,
3✔
892
                        $tokenIndent,
3✔
893
                    ];
2✔
894
                }//end if
895

896
                if ($this->debug === true) {
3✔
897
                    $line    = $tokens[$checkToken]['line'];
×
898
                    $message = vsprintf($error, $data);
×
899
                    echo "[Line $line] $message".PHP_EOL;
×
900
                }
901

902
                // Assume the change would be applied and continue
903
                // checking indents under this assumption. This gives more
904
                // technically accurate error messages.
905
                $adjustments[$checkToken] = ($checkIndent - $tokenIndent);
3✔
906

907
                $fix = $phpcsFile->addFixableError($error, $checkToken, $type, $data);
3✔
908
                if ($fix === true || $this->debug === true) {
3✔
909
                    $accepted = $this->adjustIndent($phpcsFile, $checkToken, $checkIndent, ($checkIndent - $tokenIndent));
3✔
910

911
                    if ($accepted === true && $this->debug === true) {
3✔
912
                        $line = $tokens[$checkToken]['line'];
×
913
                        $type = $tokens[$checkToken]['type'];
×
914
                        echo "\t=> add adjustment of ".$adjustments[$checkToken]." for token $checkToken ($type) on line $line".PHP_EOL;
×
915
                    }
916
                }
917
            }//end if
918

919
            if ($checkToken !== null) {
3✔
920
                $i = $checkToken;
3✔
921
            }
922

923
            // Don't check indents exactly between arrays as they tend to have custom rules.
924
            if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
3✔
925
                $disableExactStack[$tokens[$i]['bracket_closer']] = $tokens[$i]['bracket_closer'];
3✔
926
                $disableExactEnd = max($disableExactEnd, $tokens[$i]['bracket_closer']);
3✔
927
                if ($this->debug === true) {
3✔
928
                    $line    = $tokens[$i]['line'];
×
929
                    $type    = $tokens[$disableExactEnd]['type'];
×
930
                    $endLine = $tokens[$disableExactEnd]['line'];
×
931
                    echo "Opening short array bracket found on line $line".PHP_EOL;
×
932
                    if ($disableExactEnd === $tokens[$i]['bracket_closer']) {
×
933
                        echo "\t=> disabling exact indent checking until $disableExactEnd ($type) on line $endLine".PHP_EOL;
×
934
                    } else {
935
                        echo "\t=> continuing to disable exact indent checking until $disableExactEnd ($type) on line $endLine".PHP_EOL;
×
936
                    }
937
                }
938
            }
939

940
            // Completely skip here/now docs as the indent is a part of the
941
            // content itself.
942
            if ($tokens[$i]['code'] === T_START_HEREDOC
3✔
943
                || $tokens[$i]['code'] === T_START_NOWDOC
3✔
944
            ) {
945
                if ($this->debug === true) {
3✔
946
                    $line = $tokens[$i]['line'];
×
947
                    echo "Here/nowdoc found on line $line".PHP_EOL;
×
948
                }
949

950
                $i    = $phpcsFile->findNext([T_END_HEREDOC, T_END_NOWDOC], ($i + 1));
3✔
951
                $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
3✔
952
                if ($tokens[$next]['code'] === T_COMMA) {
3✔
953
                    $i = $next;
3✔
954
                }
955

956
                if ($this->debug === true) {
3✔
957
                    $line = $tokens[$i]['line'];
×
958
                    $type = $tokens[$i]['type'];
×
959
                    echo "\t* skipping to token $i ($type) on line $line *".PHP_EOL;
×
960
                }
961

962
                continue;
3✔
963
            }//end if
964

965
            // Completely skip multi-line strings as the indent is a part of the
966
            // content itself.
967
            if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING
3✔
968
                || $tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING
3✔
969
            ) {
970
                $nextNonTextString = $phpcsFile->findNext($tokens[$i]['code'], ($i + 1), null, true);
3✔
971
                if ($nextNonTextString !== false) {
3✔
972
                    $i = ($nextNonTextString - 1);
3✔
973
                }
974

975
                continue;
3✔
976
            }
977

978
            // Completely skip doc comments as they tend to have complex
979
            // indentation rules.
980
            if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG) {
3✔
981
                $i = $tokens[$i]['comment_closer'];
3✔
982
                continue;
3✔
983
            }
984

985
            // Open tags reset the indent level.
986
            if ($tokens[$i]['code'] === T_OPEN_TAG
3✔
987
                || $tokens[$i]['code'] === T_OPEN_TAG_WITH_ECHO
3✔
988
            ) {
989
                if ($this->debug === true) {
3✔
990
                    $line = $tokens[$i]['line'];
×
991
                    echo "Open PHP tag found on line $line".PHP_EOL;
×
992
                }
993

994
                if ($checkToken === null) {
3✔
995
                    $first         = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true);
3✔
996
                    $currentIndent = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])));
3✔
997
                } else {
998
                    $currentIndent = ($tokens[$i]['column'] - 1);
3✔
999
                }
1000

1001
                $lastOpenTag = $i;
3✔
1002

1003
                if (isset($adjustments[$i]) === true) {
3✔
1004
                    $currentIndent += $adjustments[$i];
3✔
1005
                }
1006

1007
                // Make sure it is divisible by our expected indent.
1008
                $currentIndent  = (int) (ceil($currentIndent / $this->indent) * $this->indent);
3✔
1009
                $setIndents[$i] = $currentIndent;
3✔
1010

1011
                if ($this->debug === true) {
3✔
1012
                    $type = $tokens[$i]['type'];
×
1013
                    echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
×
1014
                }
1015

1016
                continue;
3✔
1017
            }//end if
1018

1019
            // Close tags reset the indent level, unless they are closing a tag
1020
            // opened on the same line.
1021
            if ($tokens[$i]['code'] === T_CLOSE_TAG) {
3✔
1022
                if ($this->debug === true) {
3✔
1023
                    $line = $tokens[$i]['line'];
×
1024
                    echo "Close PHP tag found on line $line".PHP_EOL;
×
1025
                }
1026

1027
                if ($tokens[$lastOpenTag]['line'] !== $tokens[$i]['line']) {
3✔
1028
                    $currentIndent = ($tokens[$i]['column'] - 1);
3✔
1029
                    $lastCloseTag  = $i;
3✔
1030
                } else {
1031
                    if ($lastCloseTag === null) {
3✔
1032
                        $currentIndent = 0;
3✔
1033
                    } else {
1034
                        $currentIndent = ($tokens[$lastCloseTag]['column'] - 1);
3✔
1035
                    }
1036
                }
1037

1038
                if (isset($adjustments[$i]) === true) {
3✔
1039
                    $currentIndent += $adjustments[$i];
3✔
1040
                }
1041

1042
                // Make sure it is divisible by our expected indent.
1043
                $currentIndent  = (int) (ceil($currentIndent / $this->indent) * $this->indent);
3✔
1044
                $setIndents[$i] = $currentIndent;
3✔
1045

1046
                if ($this->debug === true) {
3✔
1047
                    $type = $tokens[$i]['type'];
×
1048
                    echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
×
1049
                }
1050

1051
                continue;
3✔
1052
            }//end if
1053

1054
            // Anon classes and functions set the indent based on their own indent level.
1055
            if ($tokens[$i]['code'] === T_CLOSURE || $tokens[$i]['code'] === T_ANON_CLASS) {
3✔
1056
                $closer = $tokens[$i]['scope_closer'];
3✔
1057
                if ($tokens[$i]['line'] === $tokens[$closer]['line']) {
3✔
UNCOV
1058
                    if ($this->debug === true) {
×
1059
                        $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2)));
×
1060
                        $line = $tokens[$i]['line'];
×
1061
                        echo "* ignoring single-line $type on line $line *".PHP_EOL;
×
1062
                    }
1063

UNCOV
1064
                    $i = $closer;
×
UNCOV
1065
                    continue;
×
1066
                }
1067

1068
                if ($this->debug === true) {
3✔
1069
                    $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2)));
×
1070
                    $line = $tokens[$i]['line'];
×
1071
                    echo "Open $type on line $line".PHP_EOL;
×
1072
                }
1073

1074
                $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true);
3✔
1075
                if ($this->debug === true) {
3✔
1076
                    $line = $tokens[$first]['line'];
×
1077
                    $type = $tokens[$first]['type'];
×
1078
                    echo "\t* first token is $first ($type) on line $line *".PHP_EOL;
×
1079
                }
1080

1081
                while ($tokens[$first]['code'] === T_CONSTANT_ENCAPSED_STRING
3✔
1082
                    && $tokens[($first - 1)]['code'] === T_CONSTANT_ENCAPSED_STRING
3✔
1083
                ) {
1084
                    $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, ($first - 1), true);
3✔
1085
                    if ($this->debug === true) {
3✔
1086
                        $line = $tokens[$first]['line'];
×
1087
                        $type = $tokens[$first]['type'];
×
1088
                        echo "\t* found multi-line string; amended first token is $first ($type) on line $line *".PHP_EOL;
×
1089
                    }
1090
                }
1091

1092
                $currentIndent = (($tokens[$first]['column'] - 1) + $this->indent);
3✔
1093
                $openScopes[$tokens[$i]['scope_closer']] = $tokens[$i]['scope_condition'];
3✔
1094
                if ($this->debug === true) {
3✔
1095
                    $closerToken    = $tokens[$i]['scope_closer'];
×
1096
                    $closerLine     = $tokens[$closerToken]['line'];
×
1097
                    $closerType     = $tokens[$closerToken]['type'];
×
1098
                    $conditionToken = $tokens[$i]['scope_condition'];
×
1099
                    $conditionLine  = $tokens[$conditionToken]['line'];
×
1100
                    $conditionType  = $tokens[$conditionToken]['type'];
×
1101
                    echo "\t=> added open scope $closerToken ($closerType) on line $closerLine, pointing to condition $conditionToken ($conditionType) on line $conditionLine".PHP_EOL;
×
1102
                }
1103

1104
                if (isset($adjustments[$first]) === true) {
3✔
1105
                    $currentIndent += $adjustments[$first];
3✔
1106
                }
1107

1108
                // Make sure it is divisible by our expected indent.
1109
                $currentIndent = (int) (floor($currentIndent / $this->indent) * $this->indent);
3✔
1110
                $i = $tokens[$i]['scope_opener'];
3✔
1111
                $setIndents[$i] = $currentIndent;
3✔
1112

1113
                if ($this->debug === true) {
3✔
1114
                    $type = $tokens[$i]['type'];
×
1115
                    echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
×
1116
                }
1117

1118
                continue;
3✔
1119
            }//end if
1120

1121
            // Scope openers increase the indent level.
1122
            if (isset($tokens[$i]['scope_condition']) === true
3✔
1123
                && isset($tokens[$i]['scope_opener']) === true
3✔
1124
                && $tokens[$i]['scope_opener'] === $i
3✔
1125
            ) {
1126
                $closer = $tokens[$i]['scope_closer'];
3✔
1127
                if ($tokens[$i]['line'] === $tokens[$closer]['line']) {
3✔
1128
                    if ($this->debug === true) {
3✔
1129
                        $line = $tokens[$i]['line'];
×
1130
                        $type = $tokens[$i]['type'];
×
1131
                        echo "* ignoring single-line $type on line $line *".PHP_EOL;
×
1132
                    }
1133

1134
                    $i = $closer;
3✔
1135
                    continue;
3✔
1136
                }
1137

1138
                $condition = $tokens[$tokens[$i]['scope_condition']]['code'];
3✔
1139
                if ($condition === T_FN) {
3✔
1140
                    if ($this->debug === true) {
×
1141
                        $line = $tokens[$tokens[$i]['scope_condition']]['line'];
×
1142
                        echo "* ignoring arrow function on line $line *".PHP_EOL;
×
1143
                    }
1144

1145
                    $i = $closer;
×
1146
                    continue;
×
1147
                }
1148

1149
                if (isset(Tokens::$scopeOpeners[$condition]) === true
3✔
1150
                    && in_array($condition, $this->nonIndentingScopes, true) === false
3✔
1151
                ) {
1152
                    if ($this->debug === true) {
3✔
1153
                        $line = $tokens[$i]['line'];
×
1154
                        $type = $tokens[$tokens[$i]['scope_condition']]['type'];
×
1155
                        echo "Open scope ($type) on line $line".PHP_EOL;
×
1156
                    }
1157

1158
                    $currentIndent += $this->indent;
3✔
1159
                    $setIndents[$i] = $currentIndent;
3✔
1160
                    $openScopes[$tokens[$i]['scope_closer']] = $tokens[$i]['scope_condition'];
3✔
1161
                    if ($this->debug === true) {
3✔
1162
                        $closerToken    = $tokens[$i]['scope_closer'];
×
1163
                        $closerLine     = $tokens[$closerToken]['line'];
×
1164
                        $closerType     = $tokens[$closerToken]['type'];
×
1165
                        $conditionToken = $tokens[$i]['scope_condition'];
×
1166
                        $conditionLine  = $tokens[$conditionToken]['line'];
×
1167
                        $conditionType  = $tokens[$conditionToken]['type'];
×
1168
                        echo "\t=> added open scope $closerToken ($closerType) on line $closerLine, pointing to condition $conditionToken ($conditionType) on line $conditionLine".PHP_EOL;
×
1169
                    }
1170

1171
                    if ($this->debug === true) {
3✔
1172
                        $type = $tokens[$i]['type'];
×
1173
                        echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL;
×
1174
                    }
1175

1176
                    continue;
3✔
1177
                }//end if
1178
            }//end if
1179

1180
            // Closing an anon class, closure, or match.
1181
            // Each may be returned, which can confuse control structures that
1182
            // use return as a closer, like CASE statements.
1183
            if (isset($tokens[$i]['scope_condition']) === true
3✔
1184
                && $tokens[$i]['scope_closer'] === $i
3✔
1185
                && ($tokens[$tokens[$i]['scope_condition']]['code'] === T_CLOSURE
3✔
1186
                || $tokens[$tokens[$i]['scope_condition']]['code'] === T_ANON_CLASS
3✔
1187
                || $tokens[$tokens[$i]['scope_condition']]['code'] === T_MATCH)
3✔
1188
            ) {
1189
                if ($this->debug === true) {
3✔
1190
                    $type = str_replace('_', ' ', strtolower(substr($tokens[$tokens[$i]['scope_condition']]['type'], 2)));
×
1191
                    $line = $tokens[$i]['line'];
×
1192
                    echo "Close $type on line $line".PHP_EOL;
×
1193
                }
1194

1195
                $prev = false;
3✔
1196

1197
                $parens = 0;
3✔
1198
                if (isset($tokens[$i]['nested_parenthesis']) === true
3✔
1199
                    && empty($tokens[$i]['nested_parenthesis']) === false
3✔
1200
                ) {
1201
                    $parens = $tokens[$i]['nested_parenthesis'];
3✔
1202
                    end($parens);
3✔
1203
                    $parens = key($parens);
3✔
1204
                    if ($this->debug === true) {
3✔
1205
                        $line = $tokens[$parens]['line'];
×
1206
                        echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL;
×
1207
                    }
1208
                }
1209

1210
                $condition = 0;
3✔
1211
                if (isset($tokens[$i]['conditions']) === true
3✔
1212
                    && empty($tokens[$i]['conditions']) === false
3✔
1213
                ) {
1214
                    $condition = $tokens[$i]['conditions'];
3✔
1215
                    end($condition);
3✔
1216
                    $condition = key($condition);
3✔
1217
                    if ($this->debug === true) {
3✔
1218
                        $line = $tokens[$condition]['line'];
×
1219
                        $type = $tokens[$condition]['type'];
×
1220
                        echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL;
×
1221
                    }
1222
                }
1223

1224
                if ($parens > $condition) {
3✔
1225
                    if ($this->debug === true) {
3✔
1226
                        echo "\t* using parenthesis *".PHP_EOL;
×
1227
                    }
1228

1229
                    $prev      = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($parens - 1), null, true);
3✔
1230
                    $condition = 0;
3✔
1231
                } else if ($condition > 0) {
3✔
1232
                    if ($this->debug === true) {
3✔
1233
                        echo "\t* using condition *".PHP_EOL;
×
1234
                    }
1235

1236
                    $prev   = $condition;
3✔
1237
                    $parens = 0;
3✔
1238
                }//end if
1239

1240
                if ($prev === false) {
3✔
1241
                    $prev = $phpcsFile->findPrevious([T_EQUAL, T_RETURN], ($tokens[$i]['scope_condition'] - 1), null, false, null, true);
3✔
1242
                    if ($prev === false) {
3✔
1243
                        $prev = $i;
3✔
1244
                        if ($this->debug === true) {
3✔
1245
                            echo "\t* could not find a previous T_EQUAL or T_RETURN token; will use current token *".PHP_EOL;
×
1246
                        }
1247
                    }
1248
                }
1249

1250
                if ($this->debug === true) {
3✔
1251
                    $line = $tokens[$prev]['line'];
×
1252
                    $type = $tokens[$prev]['type'];
×
1253
                    echo "\t* previous token is $type on line $line *".PHP_EOL;
×
1254
                }
1255

1256
                $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
3✔
1257
                if ($this->debug === true) {
3✔
1258
                    $line = $tokens[$first]['line'];
×
1259
                    $type = $tokens[$first]['type'];
×
1260
                    echo "\t* first token on line $line is $first ($type) *".PHP_EOL;
×
1261
                }
1262

1263
                $prev = $phpcsFile->findStartOfStatement($first);
3✔
1264
                if ($prev !== $first) {
3✔
1265
                    // This is not the start of the statement.
1266
                    if ($this->debug === true) {
3✔
1267
                        $line = $tokens[$prev]['line'];
×
1268
                        $type = $tokens[$prev]['type'];
×
1269
                        echo "\t* amended previous is $type on line $line *".PHP_EOL;
×
1270
                    }
1271

1272
                    $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
3✔
1273
                    if ($this->debug === true) {
3✔
1274
                        $line = $tokens[$first]['line'];
×
1275
                        $type = $tokens[$first]['type'];
×
1276
                        echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL;
×
1277
                    }
1278
                }
1279

1280
                $currentIndent = ($tokens[$first]['column'] - 1);
3✔
1281
                if ($condition > 0) {
3✔
1282
                    $currentIndent += $this->indent;
3✔
1283
                }
1284

1285
                if (isset($tokens[$first]['scope_closer']) === true
3✔
1286
                    && $tokens[$first]['scope_closer'] === $first
3✔
1287
                ) {
1288
                    if ($this->debug === true) {
3✔
1289
                        echo "\t* first token is a scope closer *".PHP_EOL;
×
1290
                    }
1291

1292
                    if ($condition === 0 || $tokens[$condition]['scope_opener'] < $first) {
3✔
1293
                        $currentIndent = $setIndents[$first];
×
1294
                    } else if ($this->debug === true) {
3✔
1295
                        echo "\t* ignoring scope closer *".PHP_EOL;
×
1296
                    }
1297
                }
1298

1299
                // Make sure it is divisible by our expected indent.
1300
                $currentIndent      = (int) (ceil($currentIndent / $this->indent) * $this->indent);
3✔
1301
                $setIndents[$first] = $currentIndent;
3✔
1302

1303
                if ($this->debug === true) {
3✔
1304
                    $type = $tokens[$first]['type'];
×
1305
                    echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL;
×
1306
                }
1307
            }//end if
1308
        }//end for
1309

1310
        // Don't process the rest of the file.
1311
        return $phpcsFile->numTokens;
3✔
1312

1313
    }//end process()
1314

1315

1316
    /**
1317
     * Processes this test, when one of its tokens is encountered.
1318
     *
1319
     * @param \PHP_CodeSniffer\Files\File $phpcsFile All the tokens found in the document.
1320
     * @param int                         $stackPtr  The position of the current token
1321
     *                                               in the stack passed in $tokens.
1322
     * @param int                         $length    The length of the new indent.
1323
     * @param int                         $change    The difference in length between
1324
     *                                               the old and new indent.
1325
     *
1326
     * @return bool
1327
     */
1328
    protected function adjustIndent(File $phpcsFile, $stackPtr, $length, $change)
3✔
1329
    {
1330
        $tokens = $phpcsFile->getTokens();
3✔
1331

1332
        // We don't adjust indents outside of PHP.
1333
        if ($tokens[$stackPtr]['code'] === T_INLINE_HTML) {
3✔
1334
            return false;
3✔
1335
        }
1336

1337
        $padding = '';
3✔
1338
        if ($length > 0) {
3✔
1339
            if ($this->tabIndent === true) {
3✔
1340
                $numTabs = floor($length / $this->tabWidth);
3✔
1341
                if ($numTabs > 0) {
3✔
1342
                    $numSpaces = ($length - ($numTabs * $this->tabWidth));
3✔
1343
                    $padding   = str_repeat("\t", $numTabs).str_repeat(' ', $numSpaces);
3✔
1344
                }
1345
            } else {
1346
                $padding = str_repeat(' ', $length);
3✔
1347
            }
1348
        }
1349

1350
        if ($tokens[$stackPtr]['column'] === 1) {
3✔
1351
            $trimmed  = ltrim($tokens[$stackPtr]['content']);
3✔
1352
            $accepted = $phpcsFile->fixer->replaceToken($stackPtr, $padding.$trimmed);
3✔
1353
        } else {
1354
            // Easier to just replace the entire indent.
1355
            $accepted = $phpcsFile->fixer->replaceToken(($stackPtr - 1), $padding);
3✔
1356
        }
1357

1358
        if ($accepted === false) {
3✔
1359
            return false;
3✔
1360
        }
1361

1362
        if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) {
3✔
1363
            // We adjusted the start of a comment, so adjust the rest of it
1364
            // as well so the alignment remains correct.
1365
            for ($x = ($stackPtr + 1); $x < $tokens[$stackPtr]['comment_closer']; $x++) {
3✔
1366
                if ($tokens[$x]['column'] !== 1) {
3✔
1367
                    continue;
3✔
1368
                }
1369

1370
                $length = 0;
3✔
1371
                if ($tokens[$x]['code'] === T_DOC_COMMENT_WHITESPACE) {
3✔
1372
                    $length = $tokens[$x]['length'];
3✔
1373
                }
1374

1375
                $padding = ($length + $change);
3✔
1376
                if ($padding > 0) {
3✔
1377
                    if ($this->tabIndent === true) {
3✔
1378
                        $numTabs   = floor($padding / $this->tabWidth);
3✔
1379
                        $numSpaces = ($padding - ($numTabs * $this->tabWidth));
3✔
1380
                        $padding   = str_repeat("\t", $numTabs).str_repeat(' ', $numSpaces);
3✔
1381
                    } else {
1382
                        $padding = str_repeat(' ', $padding);
3✔
1383
                    }
1384
                } else {
1385
                    $padding = '';
×
1386
                }
1387

1388
                $phpcsFile->fixer->replaceToken($x, $padding);
3✔
1389
                if ($this->debug === true) {
3✔
1390
                    $length = strlen($padding);
×
1391
                    $line   = $tokens[$x]['line'];
×
1392
                    $type   = $tokens[$x]['type'];
×
1393
                    echo "\t=> Indent adjusted to $length for $type on line $line".PHP_EOL;
×
1394
                }
1395
            }//end for
1396
        }//end if
1397

1398
        return true;
3✔
1399

1400
    }//end adjustIndent()
1401

1402

1403
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc