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

PHPCSStandards / PHP_CodeSniffer / 15253296250

26 May 2025 11:55AM UTC coverage: 78.632% (+0.3%) from 78.375%
15253296250

Pull #1105

github

web-flow
Merge d9441d98f into caf806050
Pull Request #1105: Skip tests when 'git' command is not available

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

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

70.38
/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
use PHP_CodeSniffer\Util\Writers\StatusWriter;
17

18
class ScopeIndentSniff implements Sniff
19
{
20

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

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

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

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

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

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

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

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

92

93
    /**
94
     * Returns an array of tokens this test wants to listen for.
95
     *
96
     * @return array<int|string>
97
     */
98
    public function register()
3✔
99
    {
100
        return [T_OPEN_TAG];
3✔
101

102
    }//end register()
103

104

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

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

132
        $lastOpenTag       = $stackPtr;
3✔
133
        $lastCloseTag      = null;
3✔
134
        $openScopes        = [];
3✔
135
        $adjustments       = [];
3✔
136
        $setIndents        = [];
3✔
137
        $disableExactStack = [];
3✔
138
        $disableExactEnd   = 0;
3✔
139

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

149
        if ($this->debug === true) {
3✔
150
            $line = $tokens[$stackPtr]['line'];
×
151
            StatusWriter::writeNewline();
×
152
            StatusWriter::write("Start with token $stackPtr on line $line with indent $currentIndent");
×
153
        }
154

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

163
                    $token = constant($token);
×
164
                }
165

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

170
        $this->exact     = (bool) $this->exact;
3✔
171
        $this->tabIndent = (bool) $this->tabIndent;
3✔
172

173
        $checkAnnotations = $phpcsFile->config->annotations;
3✔
174

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

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

196
                $this->exact = $value;
3✔
197

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

206
                    StatusWriter::write("* token $i on line $line set exact flag to $value *");
×
207
                }
208
            }//end if
209

210
            $checkToken  = null;
3✔
211
            $checkIndent = null;
3✔
212

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

219
            $exact = $this->exact;
3✔
220

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

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

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

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

268
                if ($this->debug === true) {
3✔
269
                    $line = $tokens[$i]['line'];
×
270
                    StatusWriter::write("Closing parenthesis found on line $line");
×
271
                }
272

273
                $parenOpener = $tokens[$parenCloser]['parenthesis_opener'];
3✔
274
                if ($tokens[$parenCloser]['line'] !== $tokens[$parenOpener]['line']) {
3✔
275
                    $parens = 0;
3✔
276
                    if (isset($tokens[$parenCloser]['nested_parenthesis']) === true
3✔
277
                        && empty($tokens[$parenCloser]['nested_parenthesis']) === false
3✔
278
                    ) {
279
                        $parens = $tokens[$parenCloser]['nested_parenthesis'];
3✔
280
                        end($parens);
3✔
281
                        $parens = key($parens);
3✔
282
                        if ($this->debug === true) {
3✔
283
                            $line = $tokens[$parens]['line'];
×
284
                            StatusWriter::write("* token has nested parenthesis $parens on line $line *", 1);
×
285
                        }
286
                    }
287

288
                    $condition = 0;
3✔
289
                    if (isset($tokens[$parenCloser]['conditions']) === true
3✔
290
                        && empty($tokens[$parenCloser]['conditions']) === false
3✔
291
                        && (isset($tokens[$parenCloser]['parenthesis_owner']) === false
3✔
292
                        || $parens > 0)
3✔
293
                    ) {
294
                        $condition = $tokens[$parenCloser]['conditions'];
3✔
295
                        end($condition);
3✔
296
                        $condition = key($condition);
3✔
297
                        if ($this->debug === true) {
3✔
298
                            $line = $tokens[$condition]['line'];
×
299
                            $type = $tokens[$condition]['type'];
×
300
                            StatusWriter::write("* token is inside condition $condition ($type) on line $line *", 1);
×
301
                        }
302
                    }
303

304
                    if ($parens > $condition) {
3✔
305
                        if ($this->debug === true) {
3✔
306
                            StatusWriter::write('* using parenthesis *', 1);
×
307
                        }
308

309
                        $parenOpener = $parens;
3✔
310
                        $condition   = 0;
3✔
311
                    } else if ($condition > 0) {
3✔
312
                        if ($this->debug === true) {
3✔
313
                            StatusWriter::write('* using condition *', 1);
×
314
                        }
315

316
                        $parenOpener = $condition;
3✔
317
                        $parens      = 0;
3✔
318
                    }
319

320
                    $exact = false;
3✔
321

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

325
                    if ($condition > 0 && $lastOpenTagCondition === $condition) {
3✔
326
                        if ($this->debug === true) {
3✔
327
                            StatusWriter::write('* open tag is inside condition; using open tag *', 1);
×
328
                        }
329

330
                        $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $lastOpenTag, true);
3✔
331
                        if ($this->debug === true) {
3✔
332
                            $line = $tokens[$first]['line'];
×
333
                            $type = $tokens[$first]['type'];
×
334
                            StatusWriter::write("* first token on line $line is $first ($type) *", 1);
×
335
                        }
336

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

342
                        $currentIndent = $checkIndent;
3✔
343

344
                        if ($this->debug === true) {
3✔
345
                            $type = $tokens[$lastOpenTag]['type'];
×
346
                            StatusWriter::write("=> checking indent of $checkIndent; main indent set to $currentIndent by token $lastOpenTag ($type)", 1);
1✔
347
                        }
348
                    } else if ($condition > 0
3✔
349
                        && isset($tokens[$condition]['scope_opener']) === true
3✔
350
                        && isset($setIndents[$tokens[$condition]['scope_opener']]) === true
3✔
351
                    ) {
352
                        $checkIndent = $setIndents[$tokens[$condition]['scope_opener']];
3✔
353
                        if (isset($adjustments[$condition]) === true) {
3✔
354
                            $checkIndent += $adjustments[$condition];
×
355
                        }
356

357
                        $currentIndent = $checkIndent;
3✔
358

359
                        if ($this->debug === true) {
3✔
360
                            $type = $tokens[$condition]['type'];
×
361
                            StatusWriter::write("=> checking indent of $checkIndent; main indent set to $currentIndent by token $condition ($type)", 1);
1✔
362
                        }
363
                    } else {
364
                        $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parenOpener, true);
3✔
365

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

371
                        if ($this->debug === true) {
3✔
372
                            $line = $tokens[$first]['line'];
×
373
                            $type = $tokens[$first]['type'];
×
374
                            StatusWriter::write("* first token on line $line is $first ($type) *", 1);
×
375
                        }
376

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

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

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

408
                            if ($this->debug === true) {
3✔
409
                                $line = $tokens[$first]['line'];
×
410
                                $type = $tokens[$first]['type'];
×
411
                                StatusWriter::write("* amended first token is $first ($type) on line $line *", 1);
×
412
                            }
413
                        }//end if
414

415
                        if (isset($tokens[$first]['scope_closer']) === true
3✔
416
                            && $tokens[$first]['scope_closer'] === $first
3✔
417
                        ) {
418
                            if ($this->debug === true) {
×
419
                                StatusWriter::write('* first token is a scope closer *', 1);
×
420
                            }
421

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

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

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

436
                                $setIndents[$first] = $currentIndent;
×
437

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

451
                            $setIndents[$first] = $currentIndent;
3✔
452

453
                            if ($this->debug === true) {
3✔
454
                                $type = $tokens[$first]['type'];
×
455
                                StatusWriter::write("=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)", 1);
1✔
456
                            }
457
                        }//end if
458
                    }//end if
459
                } else if ($this->debug === true) {
3✔
460
                    StatusWriter::write(' * ignoring single-line definition *', 1);
×
461
                }//end if
462
            }//end if
463

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

476
                if ($this->debug === true) {
3✔
477
                    $line = $tokens[$arrayCloser]['line'];
×
478
                    StatusWriter::write("Closing short array bracket found on line $line");
×
479
                }
480

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

486
                    if ($this->debug === true) {
3✔
487
                        $line = $tokens[$first]['line'];
×
488
                        $type = $tokens[$first]['type'];
×
489
                        StatusWriter::write("* first token on line $line is $first ($type) *", 1);
×
490
                    }
491

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

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

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

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

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

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

548
                        $setIndents[$first] = $currentIndent;
3✔
549

550
                        if ($this->debug === true) {
3✔
551
                            $type = $tokens[$first]['type'];
×
552
                            StatusWriter::write("=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)", 1);
1✔
553
                        }
554
                    }//end if
555
                } else if ($this->debug === true) {
3✔
556
                    StatusWriter::write(' * ignoring single-line definition *', 1);
×
557
                }//end if
558
            }//end if
559

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

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

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

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

591
                    if ($this->debug === true) {
3✔
592
                        $line = $tokens[$checkToken]['line'];
×
593
                        $type = $tokens[$checkToken]['type'];
×
594
                        StatusWriter::write("Indent adjusted to $length for $type on line $line");
×
595
                    }
596

597
                    $adjustments[$checkToken] = $adjustments[$first];
3✔
598

599
                    if ($this->debug === true) {
3✔
600
                        $line = $tokens[$checkToken]['line'];
×
601
                        $type = $tokens[$checkToken]['type'];
×
602
                        StatusWriter::write('=> add adjustment of '.$adjustments[$checkToken]." for token $checkToken ($type) on line $line", 1);
×
603
                    }
604
                }//end if
605
            }//end if
606

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

626
                    StatusWriter::write("Close scope ($type) on line $line");
×
627
                }
628

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

634
                $conditionToken = array_pop($openScopes);
3✔
635
                if ($this->debug === true && $conditionToken !== null) {
3✔
636
                    $line = $tokens[$conditionToken]['line'];
×
637
                    $type = $tokens[$conditionToken]['type'];
×
638
                    StatusWriter::write("=> removed open scope $conditionToken ($type) on line $line", 1);
×
639
                }
640

641
                if (isset($tokens[$scopeCloser]['scope_condition']) === true) {
3✔
642
                    $first = $phpcsFile->findFirstOnLine([T_WHITESPACE, T_INLINE_HTML], $tokens[$scopeCloser]['scope_condition'], true);
3✔
643
                    if ($this->debug === true) {
3✔
644
                        $line = $tokens[$first]['line'];
×
645
                        $type = $tokens[$first]['type'];
×
646
                        StatusWriter::write("* first token is $first ($type) on line $line *", 1);
×
647
                    }
648

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

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

665
                    $setIndents[$scopeCloser] = $currentIndent;
3✔
666

667
                    if ($this->debug === true) {
3✔
668
                        $type = $tokens[$scopeCloser]['type'];
×
669
                        StatusWriter::write("=> indent set to $currentIndent by token $scopeCloser ($type)", 1);
×
670
                    }
671

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

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

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

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

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

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

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

735
                    if ($this->debug === true) {
3✔
736
                        $line = $tokens[$i]['line'];
×
737
                        StatusWriter::write("Closure found on line $line");
×
738
                        StatusWriter::write("=> checking indent of $checkIndent; main indent remains at $currentIndent", 1);
×
739
                    }
740
                }
741
            }//end if
742

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

767
                    if ($isMethodPrefix === true) {
3✔
768
                        if ($this->debug === true) {
3✔
769
                            $line = $tokens[$checkToken]['line'];
×
770
                            $type = $tokens[$checkToken]['type'];
×
771
                            StatusWriter::write("* method prefix ($type) found on line $line; indent set to exact *", 1);
×
772
                        }
773

774
                        $exact = true;
3✔
775
                    }
776
                }//end if
777
            }//end if
778

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

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

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

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

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

834
            if ($checkIndent === null) {
3✔
835
                $checkIndent = $currentIndent;
3✔
836
            }
837

838
            /*
839
                The indent of the line is checked by the following IF block.
840

841
                Up until now, we've just been figuring out what the indent
842
                of this line should be.
843

844
                After this IF block, we adjust the indent again for
845
                the checking of future lines
846
            */
847

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

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

894
                if ($this->debug === true) {
3✔
895
                    $line    = $tokens[$checkToken]['line'];
×
896
                    $message = vsprintf($error, $data);
×
897
                    StatusWriter::write("[Line $line] $message");
×
898
                }
899

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

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

909
                    if ($accepted === true && $this->debug === true) {
3✔
910
                        $line = $tokens[$checkToken]['line'];
×
911
                        $type = $tokens[$checkToken]['type'];
×
912
                        StatusWriter::write('=> add adjustment of '.$adjustments[$checkToken]." for token $checkToken ($type) on line $line", 1);
×
913
                    }
914
                }
915
            }//end if
916

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

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

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

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

954
                if ($this->debug === true) {
3✔
955
                    $line = $tokens[$i]['line'];
×
956
                    $type = $tokens[$i]['type'];
×
957
                    StatusWriter::write("* skipping to token $i ($type) on line $line *", 1);
×
958
                }
959

960
                continue;
3✔
961
            }//end if
962

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

973
                continue;
3✔
974
            }
975

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

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

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

999
                $lastOpenTag = $i;
3✔
1000

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

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

1009
                if ($this->debug === true) {
3✔
1010
                    $type = $tokens[$i]['type'];
×
1011
                    StatusWriter::write("=> indent set to $currentIndent by token $i ($type)", 1);
×
1012
                }
1013

1014
                continue;
3✔
1015
            }//end if
1016

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

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

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

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

1044
                if ($this->debug === true) {
3✔
1045
                    $type = $tokens[$i]['type'];
×
1046
                    StatusWriter::write("=> indent set to $currentIndent by token $i ($type)", 1);
×
1047
                }
1048

1049
                continue;
3✔
1050
            }//end if
1051

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

1062
                    $i = $closer;
×
1063
                    continue;
×
1064
                }
1065

1066
                if ($this->debug === true) {
3✔
1067
                    $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2)));
×
1068
                    $line = $tokens[$i]['line'];
×
1069
                    StatusWriter::write("Open $type on line $line");
×
1070
                }
1071

1072
                $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true);
3✔
1073
                if ($this->debug === true) {
3✔
1074
                    $line = $tokens[$first]['line'];
×
1075
                    $type = $tokens[$first]['type'];
×
1076
                    StatusWriter::write("* first token is $first ($type) on line $line *", 1);
×
1077
                }
1078

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

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

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

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

1111
                if ($this->debug === true) {
3✔
1112
                    $type = $tokens[$i]['type'];
×
1113
                    StatusWriter::write("=> indent set to $currentIndent by token $i ($type)", 1);
×
1114
                }
1115

1116
                continue;
3✔
1117
            }//end if
1118

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

1132
                    $i = $closer;
3✔
1133
                    continue;
3✔
1134
                }
1135

1136
                $condition = $tokens[$tokens[$i]['scope_condition']]['code'];
3✔
1137
                if ($condition === T_FN) {
3✔
1138
                    if ($this->debug === true) {
×
1139
                        $line = $tokens[$tokens[$i]['scope_condition']]['line'];
×
1140
                        StatusWriter::write("* ignoring arrow function on line $line *");
×
1141
                    }
1142

1143
                    $i = $closer;
×
1144
                    continue;
×
1145
                }
1146

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

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

1169
                    if ($this->debug === true) {
3✔
1170
                        $type = $tokens[$i]['type'];
×
1171
                        StatusWriter::write("=> indent set to $currentIndent by token $i ($type)", 1);
×
1172
                    }
1173

1174
                    continue;
3✔
1175
                }//end if
1176
            }//end if
1177

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

1193
                $prev = false;
3✔
1194

1195
                $parens = 0;
3✔
1196
                if (isset($tokens[$i]['nested_parenthesis']) === true
3✔
1197
                    && empty($tokens[$i]['nested_parenthesis']) === false
3✔
1198
                ) {
1199
                    $parens = $tokens[$i]['nested_parenthesis'];
3✔
1200
                    end($parens);
3✔
1201
                    $parens = key($parens);
3✔
1202
                    if ($this->debug === true) {
3✔
1203
                        $line = $tokens[$parens]['line'];
×
1204
                        StatusWriter::write("* token has nested parenthesis $parens on line $line *", 1);
×
1205
                    }
1206
                }
1207

1208
                $condition = 0;
3✔
1209
                if (isset($tokens[$i]['conditions']) === true
3✔
1210
                    && empty($tokens[$i]['conditions']) === false
3✔
1211
                ) {
1212
                    $condition = $tokens[$i]['conditions'];
3✔
1213
                    end($condition);
3✔
1214
                    $condition = key($condition);
3✔
1215
                    if ($this->debug === true) {
3✔
1216
                        $line = $tokens[$condition]['line'];
×
1217
                        $type = $tokens[$condition]['type'];
×
1218
                        StatusWriter::write("* token is inside condition $condition ($type) on line $line *", 1);
×
1219
                    }
1220
                }
1221

1222
                if ($parens > $condition) {
3✔
1223
                    if ($this->debug === true) {
3✔
1224
                        StatusWriter::write('* using parenthesis *', 1);
×
1225
                    }
1226

1227
                    $prev      = $phpcsFile->findPrevious(Tokens::EMPTY_TOKENS, ($parens - 1), null, true);
3✔
1228
                    $condition = 0;
3✔
1229
                } else if ($condition > 0) {
3✔
1230
                    if ($this->debug === true) {
3✔
1231
                        StatusWriter::write('* using condition *', 1);
×
1232
                    }
1233

1234
                    $prev   = $condition;
3✔
1235
                    $parens = 0;
3✔
1236
                }//end if
1237

1238
                if ($prev === false) {
3✔
1239
                    $prev = $phpcsFile->findPrevious([T_EQUAL, T_RETURN], ($tokens[$i]['scope_condition'] - 1), null, false, null, true);
3✔
1240
                    if ($prev === false) {
3✔
1241
                        $prev = $i;
3✔
1242
                        if ($this->debug === true) {
3✔
1243
                            StatusWriter::write('* could not find a previous T_EQUAL or T_RETURN token; will use current token *', 1);
×
1244
                        }
1245
                    }
1246
                }
1247

1248
                if ($this->debug === true) {
3✔
1249
                    $line = $tokens[$prev]['line'];
×
1250
                    $type = $tokens[$prev]['type'];
×
1251
                    StatusWriter::write("* previous token is $type on line $line *", 1);
×
1252
                }
1253

1254
                $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
3✔
1255
                if ($this->debug === true) {
3✔
1256
                    $line = $tokens[$first]['line'];
×
1257
                    $type = $tokens[$first]['type'];
×
1258
                    StatusWriter::write("* first token on line $line is $first ($type) *", 1);
×
1259
                }
1260

1261
                $prev = $phpcsFile->findStartOfStatement($first);
3✔
1262
                if ($prev !== $first) {
3✔
1263
                    // This is not the start of the statement.
1264
                    if ($this->debug === true) {
3✔
1265
                        $line = $tokens[$prev]['line'];
×
1266
                        $type = $tokens[$prev]['type'];
×
1267
                        StatusWriter::write("* amended previous is $type on line $line *", 1);
×
1268
                    }
1269

1270
                    $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true);
3✔
1271
                    if ($this->debug === true) {
3✔
1272
                        $line = $tokens[$first]['line'];
×
1273
                        $type = $tokens[$first]['type'];
×
1274
                        StatusWriter::write("* amended first token is $first ($type) on line $line *", 1);
×
1275
                    }
1276
                }
1277

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

1283
                if (isset($tokens[$first]['scope_closer']) === true
3✔
1284
                    && $tokens[$first]['scope_closer'] === $first
3✔
1285
                ) {
1286
                    if ($this->debug === true) {
3✔
1287
                        StatusWriter::write('* first token is a scope closer *', 1);
×
1288
                    }
1289

1290
                    if ($condition === 0 || $tokens[$condition]['scope_opener'] < $first) {
3✔
1291
                        $currentIndent = $setIndents[$first];
×
1292
                    } else if ($this->debug === true) {
3✔
1293
                        StatusWriter::write('* ignoring scope closer *', 1);
×
1294
                    }
1295
                }
1296

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

1301
                if ($this->debug === true) {
3✔
1302
                    $type = $tokens[$first]['type'];
×
1303
                    StatusWriter::write("=> indent set to $currentIndent by token $first ($type)", 1);
×
1304
                }
1305
            }//end if
1306
        }//end for
1307

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

1311
    }//end process()
1312

1313

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

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

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

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

1356
        if ($accepted === false) {
3✔
1357
            return false;
3✔
1358
        }
1359

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

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

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

1386
                $phpcsFile->fixer->replaceToken($x, $padding);
3✔
1387
                if ($this->debug === true) {
3✔
1388
                    $length = strlen($padding);
×
1389
                    $line   = $tokens[$x]['line'];
×
1390
                    $type   = $tokens[$x]['type'];
×
1391
                    StatusWriter::write("=> Indent adjusted to $length for $type on line $line", 1);
×
1392
                }
1393
            }//end for
1394
        }//end if
1395

1396
        return true;
3✔
1397

1398
    }//end adjustIndent()
1399

1400

1401
}//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

© 2025 Coveralls, Inc