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

PHPCSStandards / PHP_CodeSniffer / 14516416464

17 Apr 2025 01:09PM UTC coverage: 77.945% (+0.3%) from 77.666%
14516416464

push

github

web-flow
Merge pull request #1010 from PHPCSStandards/phpcs-4.0/feature/sq-1612-stdout-vs-stderr

(Nearly) All status, debug, and progress output is now sent to STDERR instead of STDOUT

63 of 457 new or added lines in 18 files covered. (13.79%)

1 existing line in 1 file now uncovered.

19455 of 24960 relevant lines covered (77.94%)

78.64 hits per line

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

60.15
/src/Tokenizers/Tokenizer.php
1
<?php
2
/**
3
 * The base tokenizer class.
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\Tokenizers;
11

12
use PHP_CodeSniffer\Exceptions\TokenizerException;
13
use PHP_CodeSniffer\Util\Common;
14
use PHP_CodeSniffer\Util\Tokens;
15
use PHP_CodeSniffer\Util\Writers\StatusWriter;
16

17
abstract class Tokenizer
18
{
19

20
    /**
21
     * The config data for the run.
22
     *
23
     * @var \PHP_CodeSniffer\Config
24
     */
25
    protected $config = null;
26

27
    /**
28
     * The EOL char used in the content.
29
     *
30
     * @var string
31
     */
32
    protected $eolChar = '';
33

34
    /**
35
     * A token-based representation of the content.
36
     *
37
     * @var array
38
     */
39
    protected $tokens = [];
40

41
    /**
42
     * The number of tokens in the tokens array.
43
     *
44
     * @var integer
45
     */
46
    protected $numTokens = 0;
47

48
    /**
49
     * A list of tokens that are allowed to open a scope.
50
     *
51
     * @var array
52
     */
53
    public $scopeOpeners = [];
54

55
    /**
56
     * A list of tokens that end the scope.
57
     *
58
     * @var array
59
     */
60
    public $endScopeTokens = [];
61

62
    /**
63
     * Known lengths of tokens.
64
     *
65
     * @var array<string|int, int>
66
     */
67
    public $knownLengths = [];
68

69
    /**
70
     * A list of lines being ignored due to error suppression comments.
71
     *
72
     * @var array
73
     */
74
    public $ignoredLines = [];
75

76

77
    /**
78
     * Initialise and run the tokenizer.
79
     *
80
     * @param string                       $content The content to tokenize.
81
     * @param \PHP_CodeSniffer\Config|null $config  The config data for the run.
82
     * @param string                       $eolChar The EOL char used in the content.
83
     *
84
     * @return void
85
     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the file appears to be minified.
86
     */
87
    public function __construct($content, $config, $eolChar='\n')
×
88
    {
89
        $this->eolChar = $eolChar;
×
90

91
        $this->config = $config;
×
92
        $this->tokens = $this->tokenize($content);
×
93

94
        if ($config === null) {
×
95
            return;
×
96
        }
97

98
        $this->createPositionMap();
×
99
        $this->createTokenMap();
×
100
        $this->createParenthesisNestingMap();
×
101
        $this->createScopeMap();
×
102
        $this->createLevelMap();
×
103

104
        // Allow the tokenizer to do additional processing if required.
105
        $this->processAdditional();
×
106

107
    }//end __construct()
108

109

110
    /**
111
     * Checks the content to see if it looks minified.
112
     *
113
     * @param string $content The content to tokenize.
114
     * @param string $eolChar The EOL char used in the content.
115
     *
116
     * @return boolean
117
     */
118
    protected function isMinifiedContent($content, $eolChar='\n')
×
119
    {
120
        // Minified files often have a very large number of characters per line
121
        // and cause issues when tokenizing.
122
        $numChars = strlen($content);
×
123
        $numLines = (substr_count($content, $eolChar) + 1);
×
124
        $average  = ($numChars / $numLines);
×
125
        if ($average > 100) {
×
126
            return true;
×
127
        }
128

129
        return false;
×
130

131
    }//end isMinifiedContent()
132

133

134
    /**
135
     * Gets the array of tokens.
136
     *
137
     * @return array
138
     */
139
    public function getTokens()
×
140
    {
141
        return $this->tokens;
×
142

143
    }//end getTokens()
144

145

146
    /**
147
     * Creates an array of tokens when given some content.
148
     *
149
     * @param string $string The string to tokenize.
150
     *
151
     * @return array
152
     */
153
    abstract protected function tokenize($string);
154

155

156
    /**
157
     * Performs additional processing after main tokenizing.
158
     *
159
     * @return void
160
     */
161
    abstract protected function processAdditional();
162

163

164
    /**
165
     * Sets token position information.
166
     *
167
     * Can also convert tabs into spaces. Each tab can represent between
168
     * 1 and $width spaces, so this cannot be a straight string replace.
169
     *
170
     * @return void
171
     */
172
    private function createPositionMap()
366✔
173
    {
174
        $currColumn = 1;
366✔
175
        $lineNumber = 1;
366✔
176
        $eolLen     = strlen($this->eolChar);
366✔
177
        $ignoring   = null;
366✔
178
        $inTests    = defined('PHP_CODESNIFFER_IN_TESTS');
366✔
179

180
        $checkEncoding = false;
366✔
181
        if (function_exists('iconv_strlen') === true) {
366✔
182
            $checkEncoding = true;
366✔
183
        }
184

185
        $checkAnnotations = $this->config->annotations;
366✔
186
        $encoding         = $this->config->encoding;
366✔
187
        $tabWidth         = $this->config->tabWidth;
366✔
188

189
        $tokensWithTabs = [
248✔
190
            T_WHITESPACE               => true,
366✔
191
            T_COMMENT                  => true,
366✔
192
            T_DOC_COMMENT              => true,
366✔
193
            T_DOC_COMMENT_WHITESPACE   => true,
366✔
194
            T_DOC_COMMENT_STRING       => true,
366✔
195
            T_CONSTANT_ENCAPSED_STRING => true,
366✔
196
            T_DOUBLE_QUOTED_STRING     => true,
366✔
197
            T_START_HEREDOC            => true,
366✔
198
            T_START_NOWDOC             => true,
366✔
199
            T_HEREDOC                  => true,
366✔
200
            T_NOWDOC                   => true,
366✔
201
            T_END_HEREDOC              => true,
366✔
202
            T_END_NOWDOC               => true,
366✔
203
            T_INLINE_HTML              => true,
366✔
204
            T_YIELD_FROM               => true,
366✔
205
        ];
248✔
206

207
        $this->numTokens = count($this->tokens);
366✔
208
        for ($i = 0; $i < $this->numTokens; $i++) {
366✔
209
            $this->tokens[$i]['line']   = $lineNumber;
366✔
210
            $this->tokens[$i]['column'] = $currColumn;
366✔
211

212
            if (isset($this->knownLengths[$this->tokens[$i]['code']]) === true) {
366✔
213
                // There are no tabs in the tokens we know the length of.
214
                $length      = $this->knownLengths[$this->tokens[$i]['code']];
351✔
215
                $currColumn += $length;
351✔
216
            } else if ($tabWidth === 0
366✔
217
                || isset($tokensWithTabs[$this->tokens[$i]['code']]) === false
42✔
218
                || strpos($this->tokens[$i]['content'], "\t") === false
366✔
219
            ) {
220
                // There are no tabs in this content, or we aren't replacing them.
221
                if ($checkEncoding === true) {
366✔
222
                    // Not using the default encoding, so take a bit more care.
223
                    $oldLevel = error_reporting();
366✔
224
                    error_reporting(0);
366✔
225
                    $length = iconv_strlen($this->tokens[$i]['content'], $encoding);
366✔
226
                    error_reporting($oldLevel);
366✔
227

228
                    if ($length === false) {
366✔
229
                        // String contained invalid characters, so revert to default.
230
                        $length = strlen($this->tokens[$i]['content']);
242✔
231
                    }
232
                } else {
233
                    $length = strlen($this->tokens[$i]['content']);
×
234
                }
235

236
                $currColumn += $length;
366✔
237
            } else {
238
                $this->replaceTabsInToken($this->tokens[$i]);
42✔
239
                $length      = $this->tokens[$i]['length'];
42✔
240
                $currColumn += $length;
42✔
241
            }//end if
242

243
            $this->tokens[$i]['length'] = $length;
366✔
244

245
            if (isset($this->knownLengths[$this->tokens[$i]['code']]) === false
366✔
246
                && strpos($this->tokens[$i]['content'], $this->eolChar) !== false
366✔
247
            ) {
248
                $lineNumber++;
366✔
249
                $currColumn = 1;
366✔
250

251
                // Newline chars are not counted in the token length.
252
                $this->tokens[$i]['length'] -= $eolLen;
366✔
253
            }
254

255
            if ($this->tokens[$i]['code'] === T_COMMENT
366✔
256
                || $this->tokens[$i]['code'] === T_DOC_COMMENT_STRING
366✔
257
                || $this->tokens[$i]['code'] === T_DOC_COMMENT_TAG
366✔
258
                || ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML)
366✔
259
            ) {
260
                $commentText      = ltrim($this->tokens[$i]['content'], " \t/*#");
354✔
261
                $commentText      = rtrim($commentText, " */\t\r\n");
354✔
262
                $commentTextLower = strtolower($commentText);
354✔
263
                if (substr($commentTextLower, 0, 6) === 'phpcs:'
354✔
264
                    || substr($commentTextLower, 0, 7) === '@phpcs:'
354✔
265
                ) {
266
                    // If the @phpcs: syntax is being used, strip the @ to make
267
                    // comparisons easier.
268
                    if ($commentText[0] === '@') {
261✔
269
                        $commentText      = substr($commentText, 1);
66✔
270
                        $commentTextLower = strtolower($commentText);
66✔
271
                    }
272

273
                    // If there is a comment on the end, strip it off.
274
                    $commentStart = strpos($commentTextLower, ' --');
261✔
275
                    if ($commentStart !== false) {
261✔
276
                        $commentText      = substr($commentText, 0, $commentStart);
12✔
277
                        $commentTextLower = strtolower($commentText);
12✔
278
                    }
279

280
                    // If this comment is the only thing on the line, it tells us
281
                    // to ignore the following line. If the line contains other content
282
                    // then we are just ignoring this one single line.
283
                    $lineHasOtherContent = false;
261✔
284
                    $lineHasOtherTokens  = false;
261✔
285
                    if ($i > 0) {
261✔
286
                        for ($prev = ($i - 1); $prev > 0; $prev--) {
261✔
287
                            if ($this->tokens[$prev]['line'] !== $this->tokens[$i]['line']) {
192✔
288
                                // Changed lines.
289
                                break;
159✔
290
                            }
291

292
                            if ($this->tokens[$prev]['code'] === T_WHITESPACE
111✔
293
                                || $this->tokens[$prev]['code'] === T_DOC_COMMENT_WHITESPACE
51✔
294
                                || ($this->tokens[$prev]['code'] === T_INLINE_HTML
91✔
295
                                && trim($this->tokens[$prev]['content']) === '')
111✔
296
                            ) {
297
                                continue;
111✔
298
                            }
299

300
                            $lineHasOtherTokens = true;
51✔
301

302
                            if ($this->tokens[$prev]['code'] === T_OPEN_TAG
51✔
303
                                || $this->tokens[$prev]['code'] === T_DOC_COMMENT_STAR
51✔
304
                            ) {
305
                                continue;
18✔
306
                            }
307

308
                            $lineHasOtherContent = true;
33✔
309
                            break;
33✔
310
                        }//end for
311

312
                        $changedLines = false;
261✔
313
                        for ($next = $i; $next < $this->numTokens; $next++) {
261✔
314
                            if ($changedLines === true) {
261✔
315
                                // Changed lines.
316
                                break;
237✔
317
                            }
318

319
                            if (isset($this->knownLengths[$this->tokens[$next]['code']]) === false
261✔
320
                                && strpos($this->tokens[$next]['content'], $this->eolChar) !== false
261✔
321
                            ) {
322
                                // Last token on the current line.
323
                                $changedLines = true;
237✔
324
                            }
325

326
                            if ($next === $i) {
261✔
327
                                continue;
261✔
328
                            }
329

330
                            if ($this->tokens[$next]['code'] === T_WHITESPACE
51✔
331
                                || $this->tokens[$next]['code'] === T_DOC_COMMENT_WHITESPACE
39✔
332
                                || ($this->tokens[$next]['code'] === T_INLINE_HTML
41✔
333
                                && trim($this->tokens[$next]['content']) === '')
51✔
334
                            ) {
335
                                continue;
33✔
336
                            }
337

338
                            $lineHasOtherTokens = true;
21✔
339

340
                            if ($this->tokens[$next]['code'] === T_CLOSE_TAG) {
21✔
341
                                continue;
×
342
                            }
343

344
                            $lineHasOtherContent = true;
21✔
345
                            break;
21✔
346
                        }//end for
347
                    }//end if
348

349
                    if (substr($commentTextLower, 0, 9) === 'phpcs:set') {
261✔
350
                        // Ignore standards for complete lines that change sniff settings.
351
                        if ($lineHasOtherTokens === false) {
×
352
                            $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
×
353
                        }
354

355
                        // Need to maintain case here, to get the correct sniff code.
356
                        $parts = explode(' ', substr($commentText, 10));
×
357
                        if (count($parts) >= 2) {
×
358
                            $sniffParts = explode('.', $parts[0]);
×
359
                            if (count($sniffParts) >= 3) {
×
360
                                $this->tokens[$i]['sniffCode']          = array_shift($parts);
×
361
                                $this->tokens[$i]['sniffProperty']      = array_shift($parts);
×
362
                                $this->tokens[$i]['sniffPropertyValue'] = rtrim(implode(' ', $parts), " */\r\n");
×
363
                            }
364
                        }
365

366
                        $this->tokens[$i]['code'] = T_PHPCS_SET;
×
367
                        $this->tokens[$i]['type'] = 'T_PHPCS_SET';
×
368
                    } else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile') {
261✔
369
                        // The whole file will be ignored, but at least set the correct token.
370
                        $this->tokens[$i]['code'] = T_PHPCS_IGNORE_FILE;
27✔
371
                        $this->tokens[$i]['type'] = 'T_PHPCS_IGNORE_FILE';
27✔
372
                    } else if (substr($commentTextLower, 0, 13) === 'phpcs:disable') {
234✔
373
                        if ($lineHasOtherContent === false) {
177✔
374
                            // Completely ignore the comment line.
375
                            $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
162✔
376
                        }
377

378
                        if ($ignoring === null) {
177✔
379
                            $ignoring = [];
177✔
380
                        }
381

382
                        $disabledSniffs = [];
177✔
383

384
                        $additionalText = substr($commentText, 14);
177✔
385
                        if (empty($additionalText) === true) {
177✔
386
                            $ignoring = ['.all' => true];
96✔
387
                        } else {
388
                            $parts = explode(',', $additionalText);
81✔
389
                            foreach ($parts as $sniffCode) {
81✔
390
                                $sniffCode = trim($sniffCode);
81✔
391
                                $disabledSniffs[$sniffCode] = true;
81✔
392
                                $ignoring[$sniffCode]       = true;
81✔
393

394
                                // This newly disabled sniff might be disabling an existing
395
                                // enabled exception that we are tracking.
396
                                if (isset($ignoring['.except']) === true) {
81✔
397
                                    foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) {
3✔
398
                                        if ($ignoredSniffCode === $sniffCode
3✔
399
                                            || strpos($ignoredSniffCode, $sniffCode.'.') === 0
3✔
400
                                        ) {
401
                                            unset($ignoring['.except'][$ignoredSniffCode]);
3✔
402
                                        }
403
                                    }
404

405
                                    if (empty($ignoring['.except']) === true) {
3✔
406
                                        unset($ignoring['.except']);
3✔
407
                                    }
408
                                }
409
                            }//end foreach
410
                        }//end if
411

412
                        $this->tokens[$i]['code']       = T_PHPCS_DISABLE;
177✔
413
                        $this->tokens[$i]['type']       = 'T_PHPCS_DISABLE';
177✔
414
                        $this->tokens[$i]['sniffCodes'] = $disabledSniffs;
177✔
415
                    } else if (substr($commentTextLower, 0, 12) === 'phpcs:enable') {
192✔
416
                        if ($ignoring !== null) {
135✔
417
                            $enabledSniffs = [];
132✔
418

419
                            $additionalText = substr($commentText, 13);
132✔
420
                            if (empty($additionalText) === true) {
132✔
421
                                $ignoring = null;
99✔
422
                            } else {
423
                                $parts = explode(',', $additionalText);
36✔
424
                                foreach ($parts as $sniffCode) {
36✔
425
                                    $sniffCode = trim($sniffCode);
36✔
426
                                    $enabledSniffs[$sniffCode] = true;
36✔
427

428
                                    // This new enabled sniff might remove previously disabled
429
                                    // sniffs if it is actually a standard or category of sniffs.
430
                                    foreach (array_keys($ignoring) as $ignoredSniffCode) {
36✔
431
                                        if ($ignoredSniffCode === $sniffCode
36✔
432
                                            || strpos($ignoredSniffCode, $sniffCode.'.') === 0
36✔
433
                                        ) {
434
                                            unset($ignoring[$ignoredSniffCode]);
24✔
435
                                        }
436
                                    }
437

438
                                    // This new enabled sniff might be able to clear up
439
                                    // previously enabled sniffs if it is actually a standard or
440
                                    // category of sniffs.
441
                                    if (isset($ignoring['.except']) === true) {
36✔
442
                                        foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) {
3✔
443
                                            if ($ignoredSniffCode === $sniffCode
3✔
444
                                                || strpos($ignoredSniffCode, $sniffCode.'.') === 0
3✔
445
                                            ) {
446
                                                unset($ignoring['.except'][$ignoredSniffCode]);
×
447
                                            }
448
                                        }
449
                                    }
450
                                }//end foreach
451

452
                                if (empty($ignoring) === true) {
36✔
453
                                    $ignoring = null;
21✔
454
                                } else {
455
                                    if (isset($ignoring['.except']) === true) {
15✔
456
                                        $ignoring['.except'] += $enabledSniffs;
3✔
457
                                    } else {
458
                                        $ignoring['.except'] = $enabledSniffs;
15✔
459
                                    }
460
                                }
461
                            }//end if
462

463
                            if ($lineHasOtherContent === false) {
132✔
464
                                // Completely ignore the comment line.
465
                                $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
117✔
466
                            } else {
467
                                // The comment is on the same line as the code it is ignoring,
468
                                // so respect the new ignore rules.
469
                                $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
15✔
470
                            }
471

472
                            $this->tokens[$i]['sniffCodes'] = $enabledSniffs;
132✔
473
                        }//end if
474

475
                        $this->tokens[$i]['code'] = T_PHPCS_ENABLE;
135✔
476
                        $this->tokens[$i]['type'] = 'T_PHPCS_ENABLE';
135✔
477
                    } else if (substr($commentTextLower, 0, 12) === 'phpcs:ignore') {
66✔
478
                        $ignoreRules = [];
66✔
479

480
                        $additionalText = substr($commentText, 13);
66✔
481
                        if (empty($additionalText) === true) {
66✔
482
                            $ignoreRules = ['.all' => true];
45✔
483
                        } else {
484
                            $parts = explode(',', $additionalText);
21✔
485
                            foreach ($parts as $sniffCode) {
21✔
486
                                $ignoreRules[trim($sniffCode)] = true;
21✔
487
                            }
488
                        }
489

490
                        $this->tokens[$i]['code']       = T_PHPCS_IGNORE;
66✔
491
                        $this->tokens[$i]['type']       = 'T_PHPCS_IGNORE';
66✔
492
                        $this->tokens[$i]['sniffCodes'] = $ignoreRules;
66✔
493

494
                        if ($ignoring !== null) {
66✔
495
                            $ignoreRules += $ignoring;
12✔
496
                        }
497

498
                        if ($lineHasOtherContent === false) {
66✔
499
                            // Completely ignore the comment line, and set the following
500
                            // line to include the ignore rules we've set.
501
                            $this->ignoredLines[$this->tokens[$i]['line']]       = ['.all' => true];
51✔
502
                            $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $ignoreRules;
51✔
503
                        } else {
504
                            // The comment is on the same line as the code it is ignoring,
505
                            // so respect the ignore rules it set.
506
                            $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreRules;
15✔
507
                        }
508
                    }//end if
509
                }//end if
510
            }//end if
511

512
            if ($ignoring !== null && isset($this->ignoredLines[$this->tokens[$i]['line']]) === false) {
366✔
513
                $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
177✔
514
            }
515
        }//end for
516

517
        // If annotations are being ignored, we clear out all the ignore rules
518
        // but leave the annotations tokenized as normal.
519
        if ($checkAnnotations === false) {
366✔
520
            $this->ignoredLines = [];
×
521
        }
522

523
    }//end createPositionMap()
118✔
524

525

526
    /**
527
     * Replaces tabs in original token content with spaces.
528
     *
529
     * Each tab can represent between 1 and $config->tabWidth spaces,
530
     * so this cannot be a straight string replace. The original content
531
     * is placed into an orig_content index and the new token length is also
532
     * set in the length index.
533
     *
534
     * @param array  $token    The token to replace tabs inside.
535
     * @param string $prefix   The character to use to represent the start of a tab.
536
     * @param string $padding  The character to use to represent the end of a tab.
537
     * @param int    $tabWidth The number of spaces each tab represents.
538
     *
539
     * @return void
540
     */
541
    public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth=null)
177✔
542
    {
543
        $checkEncoding = false;
177✔
544
        if (function_exists('iconv_strlen') === true) {
177✔
545
            $checkEncoding = true;
177✔
546
        }
547

548
        $currColumn = $token['column'];
177✔
549
        if ($tabWidth === null) {
177✔
550
            $tabWidth = $this->config->tabWidth;
177✔
551
            if ($tabWidth === 0) {
177✔
552
                $tabWidth = 1;
3✔
553
            }
554
        }
555

556
        if (rtrim($token['content'], "\t") === '') {
177✔
557
            // String only contains tabs, so we can shortcut the process.
558
            $numTabs = strlen($token['content']);
171✔
559

560
            $firstTabSize = ($tabWidth - (($currColumn - 1) % $tabWidth));
171✔
561
            $length       = ($firstTabSize + ($tabWidth * ($numTabs - 1)));
171✔
562
            $newContent   = $prefix.str_repeat($padding, ($length - 1));
171✔
563
        } else {
564
            // We need to determine the length of each tab.
565
            $tabs = explode("\t", $token['content']);
174✔
566

567
            $numTabs    = (count($tabs) - 1);
174✔
568
            $tabNum     = 0;
174✔
569
            $newContent = '';
174✔
570
            $length     = 0;
174✔
571

572
            foreach ($tabs as $content) {
174✔
573
                if ($content !== '') {
174✔
574
                    $newContent .= $content;
174✔
575
                    if ($checkEncoding === true) {
174✔
576
                        // Not using ASCII encoding, so take a bit more care.
577
                        $oldLevel = error_reporting();
174✔
578
                        error_reporting(0);
174✔
579
                        $contentLength = iconv_strlen($content, $this->config->encoding);
174✔
580
                        error_reporting($oldLevel);
174✔
581
                        if ($contentLength === false) {
174✔
582
                            // String contained invalid characters, so revert to default.
583
                            $contentLength = strlen($content);
117✔
584
                        }
585
                    } else {
586
                        $contentLength = strlen($content);
×
587
                    }
588

589
                    $currColumn += $contentLength;
174✔
590
                    $length     += $contentLength;
174✔
591
                }
592

593
                // The last piece of content does not have a tab after it.
594
                if ($tabNum === $numTabs) {
174✔
595
                    break;
174✔
596
                }
597

598
                // Process the tab that comes after the content.
599
                $tabNum++;
174✔
600

601
                // Move the pointer to the next tab stop.
602
                $pad         = ($tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth);
174✔
603
                $currColumn += $pad;
174✔
604
                $length     += $pad;
174✔
605
                $newContent .= $prefix.str_repeat($padding, ($pad - 1));
174✔
606
            }//end foreach
607
        }//end if
608

609
        $token['orig_content'] = $token['content'];
177✔
610
        $token['content']      = $newContent;
177✔
611
        $token['length']       = $length;
177✔
612

613
    }//end replaceTabsInToken()
59✔
614

615

616
    /**
617
     * Creates a map of brackets positions.
618
     *
619
     * @return void
620
     */
621
    private function createTokenMap()
57✔
622
    {
623
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
624
            StatusWriter::write('*** START TOKEN MAP ***', 1);
×
625
        }
626

627
        $squareOpeners   = [];
57✔
628
        $curlyOpeners    = [];
57✔
629
        $this->numTokens = count($this->tokens);
57✔
630

631
        $openers   = [];
57✔
632
        $openOwner = null;
57✔
633

634
        for ($i = 0; $i < $this->numTokens; $i++) {
57✔
635
            /*
636
                Parenthesis mapping.
637
            */
638

639
            if (isset(Tokens::$parenthesisOpeners[$this->tokens[$i]['code']]) === true) {
57✔
640
                $this->tokens[$i]['parenthesis_opener'] = null;
57✔
641
                $this->tokens[$i]['parenthesis_closer'] = null;
57✔
642
                $this->tokens[$i]['parenthesis_owner']  = $i;
57✔
643
                $openOwner = $i;
57✔
644

645
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
646
                    StatusWriter::write("=> Found parenthesis owner at $i", (count($openers) + 1));
38✔
647
                }
648
            } else if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
57✔
649
                $openers[] = $i;
57✔
650
                $this->tokens[$i]['parenthesis_opener'] = $i;
57✔
651
                if ($openOwner !== null) {
57✔
652
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
653
                        StatusWriter::write("=> Found parenthesis opener at $i for $openOwner", count($openers));
×
654
                    }
655

656
                    $this->tokens[$openOwner]['parenthesis_opener'] = $i;
57✔
657
                    $this->tokens[$i]['parenthesis_owner']          = $openOwner;
57✔
658
                    $openOwner = null;
57✔
659
                } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
660
                    StatusWriter::write("=> Found unowned parenthesis opener at $i", count($openers));
38✔
661
                }
662
            } else if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
57✔
663
                // Did we set an owner for this set of parenthesis?
664
                $numOpeners = count($openers);
57✔
665
                if ($numOpeners !== 0) {
57✔
666
                    $opener = array_pop($openers);
57✔
667
                    if (isset($this->tokens[$opener]['parenthesis_owner']) === true) {
57✔
668
                        $owner = $this->tokens[$opener]['parenthesis_owner'];
57✔
669

670
                        $this->tokens[$owner]['parenthesis_closer'] = $i;
57✔
671
                        $this->tokens[$i]['parenthesis_owner']      = $owner;
57✔
672

673
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
674
                            StatusWriter::write("=> Found parenthesis closer at $i for $owner", (count($openers) + 1));
38✔
675
                        }
676
                    } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
677
                        StatusWriter::write("=> Found unowned parenthesis closer at $i for $opener", (count($openers) + 1));
×
678
                    }
679

680
                    $this->tokens[$i]['parenthesis_opener']      = $opener;
57✔
681
                    $this->tokens[$i]['parenthesis_closer']      = $i;
57✔
682
                    $this->tokens[$opener]['parenthesis_closer'] = $i;
57✔
683
                }//end if
684
            } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE) {
57✔
685
                $openers[] = $i;
×
686
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
687
                    StatusWriter::write("=> Found attribute opener at $i", count($openers));
×
688
                }
689

690
                $this->tokens[$i]['attribute_opener'] = $i;
×
691
                $this->tokens[$i]['attribute_closer'] = null;
×
692
            } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE_END) {
57✔
693
                $numOpeners = count($openers);
×
694
                if ($numOpeners !== 0) {
×
695
                    $opener = array_pop($openers);
×
696
                    if (isset($this->tokens[$opener]['attribute_opener']) === true) {
×
697
                        $this->tokens[$opener]['attribute_closer'] = $i;
×
698

699
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
700
                            StatusWriter::write("=> Found attribute closer at $i for $opener", (count($openers) + 1));
×
701
                        }
702

703
                        for ($x = ($opener + 1); $x <= $i; ++$x) {
×
704
                            if (isset($this->tokens[$x]['attribute_closer']) === true) {
×
705
                                continue;
×
706
                            }
707

708
                            $this->tokens[$x]['attribute_opener'] = $opener;
×
709
                            $this->tokens[$x]['attribute_closer'] = $i;
×
710
                        }
711
                    } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
712
                        StatusWriter::write("=> Found unowned attribute closer at $i for $opener", (count($openers) + 1));
×
713
                    }
714
                }//end if
715
            }//end if
716

717
            /*
718
                Bracket mapping.
719
            */
720

721
            switch ($this->tokens[$i]['code']) {
57✔
722
            case T_OPEN_SQUARE_BRACKET:
57✔
723
                $squareOpeners[] = $i;
57✔
724

725
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
726
                    StatusWriter::write("=> Found square bracket opener at $i", (count($squareOpeners) + count($curlyOpeners)));
×
727
                }
728
                break;
57✔
729
            case T_OPEN_CURLY_BRACKET:
57✔
730
                if (isset($this->tokens[$i]['scope_closer']) === false) {
57✔
731
                    $curlyOpeners[] = $i;
57✔
732

733
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
734
                        StatusWriter::write("=> Found curly bracket opener at $i", (count($squareOpeners) + count($curlyOpeners)));
×
735
                    }
736
                }
737
                break;
57✔
738
            case T_CLOSE_SQUARE_BRACKET:
57✔
739
                if (empty($squareOpeners) === false) {
57✔
740
                    $opener = array_pop($squareOpeners);
57✔
741
                    $this->tokens[$i]['bracket_opener']      = $opener;
57✔
742
                    $this->tokens[$i]['bracket_closer']      = $i;
57✔
743
                    $this->tokens[$opener]['bracket_opener'] = $opener;
57✔
744
                    $this->tokens[$opener]['bracket_closer'] = $i;
57✔
745

746
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
747
                        StatusWriter::write("=> Found square bracket closer at $i for $opener", (count($squareOpeners) + count($curlyOpeners) + 1));
×
748
                    }
749
                }
750
                break;
57✔
751
            case T_CLOSE_CURLY_BRACKET:
57✔
752
                if (empty($curlyOpeners) === false
57✔
753
                    && isset($this->tokens[$i]['scope_opener']) === false
57✔
754
                ) {
755
                    $opener = array_pop($curlyOpeners);
57✔
756
                    $this->tokens[$i]['bracket_opener']      = $opener;
57✔
757
                    $this->tokens[$i]['bracket_closer']      = $i;
57✔
758
                    $this->tokens[$opener]['bracket_opener'] = $opener;
57✔
759
                    $this->tokens[$opener]['bracket_closer'] = $i;
57✔
760

761
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
762
                        StatusWriter::write("=> Found curly bracket closer at $i for $opener", (count($squareOpeners) + count($curlyOpeners) + 1));
×
763
                    }
764
                }
765
                break;
57✔
766
            default:
767
                continue 2;
57✔
768
            }//end switch
769
        }//end for
770

771
        // Cleanup for any openers that we didn't find closers for.
772
        // This typically means there was a syntax error breaking things.
773
        foreach ($openers as $opener) {
57✔
774
            unset($this->tokens[$opener]['parenthesis_opener']);
×
775
            unset($this->tokens[$opener]['parenthesis_owner']);
×
776
        }
777

778
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
NEW
779
            StatusWriter::write('*** END TOKEN MAP ***', 1);
×
780
        }
781

782
    }//end createTokenMap()
19✔
783

784

785
    /**
786
     * Creates a map for the parenthesis tokens that surround other tokens.
787
     *
788
     * @return void
789
     */
790
    private function createParenthesisNestingMap()
204✔
791
    {
792
        $map = [];
204✔
793
        for ($i = 0; $i < $this->numTokens; $i++) {
204✔
794
            if (isset($this->tokens[$i]['parenthesis_opener']) === true
204✔
795
                && $i === $this->tokens[$i]['parenthesis_opener']
204✔
796
            ) {
797
                if (empty($map) === false) {
204✔
798
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
799
                }
800

801
                if (isset($this->tokens[$i]['parenthesis_closer']) === true) {
204✔
802
                    $map[$this->tokens[$i]['parenthesis_opener']]
204✔
803
                        = $this->tokens[$i]['parenthesis_closer'];
204✔
804
                }
805
            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
204✔
806
                && $i === $this->tokens[$i]['parenthesis_closer']
204✔
807
            ) {
808
                array_pop($map);
204✔
809
                if (empty($map) === false) {
204✔
810
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
811
                }
812
            } else {
813
                if (empty($map) === false) {
204✔
814
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
815
                }
816
            }//end if
817
        }//end for
818

819
    }//end createParenthesisNestingMap()
68✔
820

821

822
    /**
823
     * Creates a scope map of tokens that open scopes.
824
     *
825
     * @return void
826
     * @see    recurseScopeMap()
827
     */
828
    private function createScopeMap()
×
829
    {
830
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
831
            StatusWriter::write('*** START SCOPE MAP ***', 1);
×
832
        }
833

834
        for ($i = 0; $i < $this->numTokens; $i++) {
×
835
            // Check to see if the current token starts a new scope.
836
            if (isset($this->scopeOpeners[$this->tokens[$i]['code']]) === true) {
×
837
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
838
                    $type    = $this->tokens[$i]['type'];
×
839
                    $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
NEW
840
                    StatusWriter::write("Start scope map at $i:$type => $content", 1);
×
841
                }
842

843
                if (isset($this->tokens[$i]['scope_condition']) === true) {
×
844
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
845
                        StatusWriter::write('* already processed, skipping *', 1);
×
846
                    }
847

848
                    continue;
×
849
                }
850

851
                $i = $this->recurseScopeMap($i);
×
852
            }//end if
853
        }//end for
854

855
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
856
            StatusWriter::write('*** END SCOPE MAP ***', 1);
×
857
        }
858

859
    }//end createScopeMap()
860

861

862
    /**
863
     * Recurses though the scope openers to build a scope map.
864
     *
865
     * @param int $stackPtr The position in the stack of the token that
866
     *                      opened the scope (eg. an IF token or FOR token).
867
     * @param int $depth    How many scope levels down we are.
868
     * @param int $ignore   How many curly braces we are ignoring.
869
     *
870
     * @return int The position in the stack that closed the scope.
871
     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the nesting level gets too deep.
872
     */
873
    private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
222✔
874
    {
875
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
NEW
876
            StatusWriter::write("=> Begin scope map recursion at token $stackPtr with depth $depth", $depth);
×
877
        }
878

879
        $opener    = null;
222✔
880
        $currType  = $this->tokens[$stackPtr]['code'];
222✔
881
        $startLine = $this->tokens[$stackPtr]['line'];
222✔
882

883
        // We will need this to restore the value if we end up
884
        // returning a token ID that causes our calling function to go back
885
        // over already ignored braces.
886
        $originalIgnore = $ignore;
222✔
887

888
        // If the start token for this scope opener is the same as
889
        // the scope token, we have already found our opener.
890
        if (isset($this->scopeOpeners[$currType]['start'][$currType]) === true) {
222✔
891
            $opener = $stackPtr;
×
892
        }
893

894
        for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) {
222✔
895
            $tokenType = $this->tokens[$i]['code'];
222✔
896

897
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
898
                $type    = $this->tokens[$i]['type'];
×
899
                $line    = $this->tokens[$i]['line'];
×
900
                $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
901

NEW
902
                $statusMessage = "Process token $i on line $line [";
×
903
                if ($opener !== null) {
×
NEW
904
                    $statusMessage .= "opener:$opener;";
×
905
                }
906

907
                if ($ignore > 0) {
×
NEW
908
                    $statusMessage .= "ignore=$ignore;";
×
909
                }
910

NEW
911
                $statusMessage .= "]: $type => $content";
×
NEW
912
                StatusWriter::write($statusMessage, $depth);
×
913
            }//end if
914

915
            // Very special case for IF statements in PHP that can be defined without
916
            // scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1;
917
            // If an IF statement below this one has an opener but no
918
            // keyword, the opener will be incorrectly assigned to this IF statement.
919
            // The same case also applies to USE statements, which don't have to have
920
            // openers, so a following USE statement can cause an incorrect brace match.
921
            if (($currType === T_IF || $currType === T_ELSE || $currType === T_USE)
222✔
922
                && $opener === null
222✔
923
                && ($this->tokens[$i]['code'] === T_SEMICOLON
210✔
924
                || $this->tokens[$i]['code'] === T_CLOSE_TAG)
222✔
925
            ) {
926
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
72✔
927
                    $type = $this->tokens[$stackPtr]['type'];
×
928
                    if ($this->tokens[$i]['code'] === T_SEMICOLON) {
×
929
                        $closerType = 'semicolon';
×
930
                    } else {
931
                        $closerType = 'close tag';
×
932
                    }
933

NEW
934
                    StatusWriter::write("=> Found $closerType before scope opener for $stackPtr:$type, bailing", $depth);
×
935
                }
936

937
                return $i;
72✔
938
            }
939

940
            // Special case for PHP control structures that have no braces.
941
            // If we find a curly brace closer before we find the opener,
942
            // we're not going to find an opener. That closer probably belongs to
943
            // a control structure higher up.
944
            if ($opener === null
222✔
945
                && $ignore === 0
222✔
946
                && $tokenType === T_CLOSE_CURLY_BRACKET
222✔
947
                && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
222✔
948
            ) {
949
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
950
                    $type = $this->tokens[$stackPtr]['type'];
×
NEW
951
                    StatusWriter::write("=> Found curly brace closer before scope opener for $stackPtr:$type, bailing", $depth);
×
952
                }
953

954
                return ($i - 1);
×
955
            }
956

957
            if ($opener !== null
222✔
958
                && (isset($this->tokens[$i]['scope_opener']) === false
222✔
959
                || $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true)
222✔
960
                && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
222✔
961
            ) {
962
                if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
222✔
963
                    // The last opening bracket must have been for a string
964
                    // offset or alike, so let's ignore it.
965
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
NEW
966
                        StatusWriter::write('* finished ignoring curly brace *', $depth);
×
967
                    }
968

969
                    $ignore--;
183✔
970
                    continue;
183✔
971
                } else if ($this->tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET
222✔
972
                    && $tokenType !== T_CLOSE_CURLY_BRACKET
222✔
973
                ) {
974
                    // The opener is a curly bracket so the closer must be a curly bracket as well.
975
                    // We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered
976
                    // a closer of T_IF when it should not.
977
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
978
                        $type = $this->tokens[$stackPtr]['type'];
×
NEW
979
                        StatusWriter::write("=> Ignoring non-curly scope closer for $stackPtr:$type", $depth);
×
980
                    }
981
                } else {
982
                    $scopeCloser = $i;
222✔
983
                    $todo        = [
148✔
984
                        $stackPtr,
222✔
985
                        $opener,
222✔
986
                    ];
148✔
987

988
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
989
                        $type       = $this->tokens[$stackPtr]['type'];
×
990
                        $closerType = $this->tokens[$scopeCloser]['type'];
×
NEW
991
                        StatusWriter::write("=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type", $depth);
×
992
                    }
993

994
                    $validCloser = true;
222✔
995
                    if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF)
222✔
996
                        && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)
222✔
997
                    ) {
998
                        // To be a closer, this token must have an opener.
999
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
NEW
1000
                            StatusWriter::write('* closer needs to be tested *', $depth);
×
1001
                        }
1002

1003
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
3✔
1004

1005
                        if (isset($this->tokens[$scopeCloser]['scope_opener']) === false) {
3✔
1006
                            $validCloser = false;
×
1007
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1008
                                StatusWriter::write('* closer is not valid (no opener found) *', $depth);
×
1009
                            }
1010
                        } else if ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
3✔
1011
                            $validCloser = false;
×
1012
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1013
                                $type       = $this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['type'];
×
1014
                                $openerType = $this->tokens[$opener]['type'];
×
NEW
1015
                                StatusWriter::write("* closer is not valid (mismatched opener type; $type != $openerType) *", $depth);
×
1016
                            }
1017
                        } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1018
                            StatusWriter::write('* closer was valid *', $depth);
2✔
1019
                        }
1020
                    } else {
1021
                        // The closer was not processed, so we need to
1022
                        // complete that token as well.
1023
                        $todo[] = $scopeCloser;
222✔
1024
                    }//end if
1025

1026
                    if ($validCloser === true) {
222✔
1027
                        foreach ($todo as $token) {
222✔
1028
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
222✔
1029
                            $this->tokens[$token]['scope_opener']    = $opener;
222✔
1030
                            $this->tokens[$token]['scope_closer']    = $scopeCloser;
222✔
1031
                        }
1032

1033
                        if ($this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true) {
222✔
1034
                            // As we are going back to where we started originally, restore
1035
                            // the ignore value back to its original value.
1036
                            $ignore = $originalIgnore;
201✔
1037
                            return $opener;
201✔
1038
                        } else if ($scopeCloser === $i
222✔
1039
                            && isset($this->scopeOpeners[$tokenType]) === true
222✔
1040
                        ) {
1041
                            // Unset scope_condition here or else the token will appear to have
1042
                            // already been processed, and it will be skipped. Normally we want that,
1043
                            // but in this case, the token is both a closer and an opener, so
1044
                            // it needs to act like an opener. This is also why we return the
1045
                            // token before this one; so the closer has a chance to be processed
1046
                            // a second time, but as an opener.
1047
                            unset($this->tokens[$scopeCloser]['scope_condition']);
×
1048
                            return ($i - 1);
×
1049
                        } else {
1050
                            return $i;
222✔
1051
                        }
1052
                    } else {
1053
                        continue;
×
1054
                    }//end if
1055
                }//end if
1056
            }//end if
1057

1058
            // Is this an opening condition ?
1059
            if (isset($this->scopeOpeners[$tokenType]) === true) {
222✔
1060
                if ($opener === null) {
222✔
1061
                    if ($tokenType === T_USE) {
147✔
1062
                        // PHP use keywords are special because they can be
1063
                        // used as blocks but also inline in function definitions.
1064
                        // So if we find them nested inside another opener, just skip them.
1065
                        continue;
×
1066
                    }
1067

1068
                    if ($tokenType === T_NAMESPACE) {
147✔
1069
                        // PHP namespace keywords are special because they can be
1070
                        // used as blocks but also inline as operators.
1071
                        // So if we find them nested inside another opener, just skip them.
1072
                        continue;
129✔
1073
                    }
1074

1075
                    if ($tokenType === T_FUNCTION
18✔
1076
                        && $this->tokens[$stackPtr]['code'] !== T_FUNCTION
18✔
1077
                    ) {
1078
                        // Probably a closure, so process it manually.
1079
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
1080
                            $type = $this->tokens[$stackPtr]['type'];
×
NEW
1081
                            StatusWriter::write("=> Found function before scope opener for $stackPtr:$type, processing manually", $depth);
×
1082
                        }
1083

1084
                        if (isset($this->tokens[$i]['scope_closer']) === true) {
18✔
1085
                            // We've already processed this closure.
1086
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1087
                                StatusWriter::write('* already processed, skipping *', $depth);
×
1088
                            }
1089

1090
                            $i = $this->tokens[$i]['scope_closer'];
×
1091
                            continue;
×
1092
                        }
1093

1094
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
18✔
1095
                        continue;
18✔
1096
                    }//end if
1097

1098
                    if ($tokenType === T_CLASS) {
×
1099
                        // Probably an anonymous class inside another anonymous class,
1100
                        // so process it manually.
1101
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1102
                            $type = $this->tokens[$stackPtr]['type'];
×
NEW
1103
                            StatusWriter::write("=> Found class before scope opener for $stackPtr:$type, processing manually", $depth);
×
1104
                        }
1105

1106
                        if (isset($this->tokens[$i]['scope_closer']) === true) {
×
1107
                            // We've already processed this anon class.
1108
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1109
                                StatusWriter::write('* already processed, skipping *', $depth);
×
1110
                            }
1111

1112
                            $i = $this->tokens[$i]['scope_closer'];
×
1113
                            continue;
×
1114
                        }
1115

1116
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
×
1117
                        continue;
×
1118
                    }//end if
1119

1120
                    // Found another opening condition but still haven't
1121
                    // found our opener, so we are never going to find one.
1122
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1123
                        $type = $this->tokens[$stackPtr]['type'];
×
NEW
1124
                        StatusWriter::write("=> Found new opening condition before scope opener for $stackPtr:$type, ", $depth, 0);
×
1125
                    }
1126

1127
                    if (($this->tokens[$stackPtr]['code'] === T_IF
×
1128
                        || $this->tokens[$stackPtr]['code'] === T_ELSEIF
×
1129
                        || $this->tokens[$stackPtr]['code'] === T_ELSE)
×
1130
                        && ($this->tokens[$i]['code'] === T_ELSE
×
1131
                        || $this->tokens[$i]['code'] === T_ELSEIF)
×
1132
                    ) {
1133
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1134
                            StatusWriter::write('continuing');
×
1135
                        }
1136

1137
                        return ($i - 1);
×
1138
                    } else {
1139
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1140
                            StatusWriter::write('backtracking');
×
1141
                        }
1142

1143
                        return $stackPtr;
×
1144
                    }
1145
                }//end if
1146

1147
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
204✔
NEW
1148
                    StatusWriter::write('* token is an opening condition *', $depth);
×
1149
                }
1150

1151
                $isShared = ($this->scopeOpeners[$tokenType]['shared'] === true);
204✔
1152

1153
                if (isset($this->tokens[$i]['scope_condition']) === true) {
204✔
1154
                    // We've been here before.
1155
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
NEW
1156
                        StatusWriter::write('* already processed, skipping *', $depth);
×
1157
                    }
1158

1159
                    if ($isShared === false
183✔
1160
                        && isset($this->tokens[$i]['scope_closer']) === true
183✔
1161
                    ) {
1162
                        $i = $this->tokens[$i]['scope_closer'];
183✔
1163
                    }
1164

1165
                    continue;
183✔
1166
                } else if ($currType === $tokenType
204✔
1167
                    && $isShared === false
204✔
1168
                    && $opener === null
204✔
1169
                ) {
1170
                    // We haven't yet found our opener, but we have found another
1171
                    // scope opener which is the same type as us, and we don't
1172
                    // share openers, so we will never find one.
1173
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1174
                        StatusWriter::write('* it was another token\'s opener, bailing *', $depth);
×
1175
                    }
1176

1177
                    return $stackPtr;
×
1178
                } else {
1179
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
204✔
NEW
1180
                        StatusWriter::write('* searching for opener *', $depth);
×
1181
                    }
1182

1183
                    if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
204✔
1184
                        $oldIgnore = $ignore;
186✔
1185
                        $ignore    = 0;
186✔
1186
                    }
1187

1188
                    // PHP has a max nesting level for functions. Stop before we hit that limit
1189
                    // because too many loops means we've run into trouble anyway.
1190
                    if ($depth > 50) {
204✔
1191
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1192
                            StatusWriter::write('* reached maximum nesting level; aborting *', $depth);
×
1193
                        }
1194

1195
                        throw new TokenizerException('Maximum nesting level reached; file could not be processed');
×
1196
                    }
1197

1198
                    $oldDepth = $depth;
204✔
1199
                    if ($isShared === true
204✔
1200
                        && isset($this->scopeOpeners[$tokenType]['with'][$currType]) === true
204✔
1201
                    ) {
1202
                        // Don't allow the depth to increment because this is
1203
                        // possibly not a true nesting if we are sharing our closer.
1204
                        // This can happen, for example, when a SWITCH has a large
1205
                        // number of CASE statements with the same shared BREAK.
1206
                        $depth--;
204✔
1207
                    }
1208

1209
                    $i     = self::recurseScopeMap($i, ($depth + 1), $ignore);
204✔
1210
                    $depth = $oldDepth;
204✔
1211

1212
                    if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
204✔
1213
                        $ignore = $oldIgnore;
186✔
1214
                    }
1215
                }//end if
1216
            }//end if
1217

1218
            if (isset($this->scopeOpeners[$currType]['start'][$tokenType]) === true
222✔
1219
                && $opener === null
222✔
1220
            ) {
1221
                if ($tokenType === T_OPEN_CURLY_BRACKET) {
222✔
1222
                    if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true
219✔
1223
                        && $i < $this->tokens[$stackPtr]['parenthesis_closer']
219✔
1224
                    ) {
1225
                        // We found a curly brace inside the condition of the
1226
                        // current scope opener, so it must be a string offset.
1227
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
NEW
1228
                            StatusWriter::write('* ignoring curly brace inside condition *', $depth);
×
1229
                        }
1230

1231
                        $ignore++;
18✔
1232
                    } else {
1233
                        // Make sure this is actually an opener and not a
1234
                        // string offset (e.g., $var{0}).
1235
                        for ($x = ($i - 1); $x > 0; $x--) {
219✔
1236
                            if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
219✔
1237
                                continue;
219✔
1238
                            } else {
1239
                                // If the first non-whitespace/comment token looks like this
1240
                                // brace is a string offset, or this brace is mid-way through
1241
                                // a new statement, it isn't a scope opener.
1242
                                $disallowed  = Tokens::$assignmentTokens;
219✔
1243
                                $disallowed += [
146✔
1244
                                    T_DOLLAR                   => true,
219✔
1245
                                    T_VARIABLE                 => true,
219✔
1246
                                    T_OBJECT_OPERATOR          => true,
219✔
1247
                                    T_NULLSAFE_OBJECT_OPERATOR => true,
219✔
1248
                                    T_COMMA                    => true,
219✔
1249
                                    T_OPEN_PARENTHESIS         => true,
219✔
1250
                                ];
146✔
1251

1252
                                if (isset($disallowed[$this->tokens[$x]['code']]) === true) {
219✔
1253
                                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1254
                                        StatusWriter::write('* ignoring curly brace *', $depth);
×
1255
                                    }
1256

1257
                                    $ignore++;
×
1258
                                }
1259

1260
                                break;
219✔
1261
                            }//end if
1262
                        }//end for
1263
                    }//end if
1264
                }//end if
1265

1266
                if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
222✔
1267
                    $openerNested = isset($this->tokens[$i]['nested_parenthesis']);
222✔
1268
                    $ownerNested  = isset($this->tokens[$stackPtr]['nested_parenthesis']);
222✔
1269

1270
                    if (($openerNested === true && $ownerNested === false)
222✔
1271
                        || ($openerNested === false && $ownerNested === true)
222✔
1272
                        || ($openerNested === true
222✔
1273
                        && $this->tokens[$i]['nested_parenthesis'] !== $this->tokens[$stackPtr]['nested_parenthesis'])
222✔
1274
                    ) {
1275
                        // We found the a token that looks like the opener, but it's nested differently.
1276
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
1277
                            $type = $this->tokens[$i]['type'];
×
1278
                            StatusWriter::write("* ignoring possible opener $i:$type as nested parenthesis don't match *", $depth);
12✔
1279
                        }
1280
                    } else {
1281
                        // We found the opening scope token for $currType.
1282
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
1283
                            $type = $this->tokens[$stackPtr]['type'];
×
NEW
1284
                            StatusWriter::write("=> Found scope opener for $stackPtr:$type", $depth);
×
1285
                        }
1286

1287
                        $opener = $i;
222✔
1288
                    }
1289
                }//end if
1290
            } else if ($tokenType === T_SEMICOLON
222✔
1291
                && $opener === null
222✔
1292
                && (isset($this->tokens[$stackPtr]['parenthesis_closer']) === false
215✔
1293
                || $i > $this->tokens[$stackPtr]['parenthesis_closer'])
222✔
1294
            ) {
1295
                // Found the end of a statement but still haven't
1296
                // found our opener, so we are never going to find one.
1297
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
1298
                    $type = $this->tokens[$stackPtr]['type'];
×
NEW
1299
                    StatusWriter::write("=> Found end of statement before scope opener for $stackPtr:$type, continuing", $depth);
×
1300
                }
1301

1302
                return ($i - 1);
201✔
1303
            } else if ($tokenType === T_OPEN_PARENTHESIS) {
222✔
1304
                if (isset($this->tokens[$i]['parenthesis_owner']) === true) {
222✔
1305
                    $owner = $this->tokens[$i]['parenthesis_owner'];
222✔
1306
                    if (isset(Tokens::$scopeOpeners[$this->tokens[$owner]['code']]) === true
222✔
1307
                        && isset($this->tokens[$i]['parenthesis_closer']) === true
222✔
1308
                    ) {
1309
                        // If we get into here, then we opened a parenthesis for
1310
                        // a scope (eg. an if or else if) so we need to update the
1311
                        // start of the line so that when we check to see
1312
                        // if the closing parenthesis is more than n lines away from
1313
                        // the statement, we check from the closing parenthesis.
1314
                        $startLine = $this->tokens[$this->tokens[$i]['parenthesis_closer']]['line'];
222✔
1315
                    }
1316
                }
1317
            } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
222✔
1318
                // We opened something that we don't have a scope opener for.
1319
                // Examples of this are curly brackets for string offsets etc.
1320
                // We want to ignore this so that we don't have an invalid scope
1321
                // map.
1322
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
NEW
1323
                    StatusWriter::write('* ignoring curly brace *', $depth);
×
1324
                }
1325

1326
                $ignore++;
183✔
1327
            } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
222✔
1328
                // We found the end token for the opener we were ignoring.
1329
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
NEW
1330
                    StatusWriter::write('* finished ignoring curly brace *', $depth);
×
1331
                }
1332

1333
                $ignore--;
18✔
1334
            } else if ($opener === null
222✔
1335
                && isset($this->scopeOpeners[$currType]) === true
222✔
1336
            ) {
1337
                // If we still haven't found the opener after 30 lines,
1338
                // we're not going to find it, unless we know it requires
1339
                // an opener (in which case we better keep looking) or the last
1340
                // token was empty (in which case we'll just confirm there is
1341
                // more code in this file and not just a big comment).
1342
                if ($this->tokens[$i]['line'] >= ($startLine + 30)
222✔
1343
                    && isset(Tokens::$emptyTokens[$this->tokens[($i - 1)]['code']]) === false
222✔
1344
                ) {
1345
                    if ($this->scopeOpeners[$currType]['strict'] === true) {
×
1346
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1347
                            $type  = $this->tokens[$stackPtr]['type'];
×
1348
                            $lines = ($this->tokens[$i]['line'] - $startLine);
×
NEW
1349
                            StatusWriter::write("=> Still looking for $stackPtr:$type scope opener after $lines lines", $depth);
×
1350
                        }
1351
                    } else {
1352
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1353
                            $type = $this->tokens[$stackPtr]['type'];
×
NEW
1354
                            StatusWriter::write("=> Couldn't find scope opener for $stackPtr:$type, bailing", $depth);
×
1355
                        }
1356

1357
                        return $stackPtr;
148✔
1358
                    }
1359
                }
1360
            } else if ($opener !== null
204✔
1361
                && $tokenType !== T_BREAK
204✔
1362
                && isset($this->endScopeTokens[$tokenType]) === true
204✔
1363
            ) {
1364
                if (isset($this->tokens[$i]['scope_condition']) === false) {
186✔
1365
                    if ($ignore > 0) {
186✔
1366
                        // We found the end token for the opener we were ignoring.
1367
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1368
                            StatusWriter::write('* finished ignoring curly brace *', $depth);
×
1369
                        }
1370

1371
                        $ignore--;
×
1372
                    } else {
1373
                        // We found a token that closes the scope but it doesn't
1374
                        // have a condition, so it belongs to another token and
1375
                        // our token doesn't have a closer, so pretend this is
1376
                        // the closer.
1377
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
186✔
1378
                            $type = $this->tokens[$stackPtr]['type'];
×
NEW
1379
                            StatusWriter::write("=> Found (unexpected) scope closer for $stackPtr:$type", $depth);
×
1380
                        }
1381

1382
                        foreach ([$stackPtr, $opener] as $token) {
186✔
1383
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
186✔
1384
                            $this->tokens[$token]['scope_opener']    = $opener;
186✔
1385
                            $this->tokens[$token]['scope_closer']    = $i;
186✔
1386
                        }
1387

1388
                        return ($i - 1);
186✔
1389
                    }//end if
1390
                }//end if
1391
            }//end if
1392
        }//end for
1393

1394
        return $stackPtr;
×
1395

1396
    }//end recurseScopeMap()
1397

1398

1399
    /**
1400
     * Constructs the level map.
1401
     *
1402
     * The level map adds a 'level' index to each token which indicates the
1403
     * depth that a token within a set of scope blocks. It also adds a
1404
     * 'conditions' index which is an array of the scope conditions that opened
1405
     * each of the scopes - position 0 being the first scope opener.
1406
     *
1407
     * @return void
1408
     */
1409
    private function createLevelMap()
×
1410
    {
1411
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1412
            StatusWriter::write('*** START LEVEL MAP ***', 1);
×
1413
        }
1414

1415
        $this->numTokens = count($this->tokens);
×
1416
        $level           = 0;
×
1417
        $conditions      = [];
×
1418
        $lastOpener      = null;
×
1419
        $openers         = [];
×
1420

1421
        for ($i = 0; $i < $this->numTokens; $i++) {
×
1422
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1423
                $type = $this->tokens[$i]['type'];
×
1424
                $line = $this->tokens[$i]['line'];
×
1425
                $len  = $this->tokens[$i]['length'];
×
1426
                $col  = $this->tokens[$i]['column'];
×
1427

1428
                $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
1429

NEW
1430
                $statusMessage = "Process token $i on line $line [col:$col;len:$len;lvl:$level;";
×
1431
                if (empty($conditions) !== true) {
×
1432
                    $conditionString = 'conds;';
×
1433
                    foreach ($conditions as $condition) {
×
1434
                        $conditionString .= Tokens::tokenName($condition).',';
×
1435
                    }
1436

NEW
1437
                    $statusMessage .= rtrim($conditionString, ',').';';
×
1438
                }
1439

NEW
1440
                $statusMessage .= "]: $type => $content";
×
NEW
1441
                StatusWriter::write($statusMessage, ($level + 1));
×
1442
            }//end if
1443

1444
            $this->tokens[$i]['level']      = $level;
×
1445
            $this->tokens[$i]['conditions'] = $conditions;
×
1446

1447
            if (isset($this->tokens[$i]['scope_condition']) === true) {
×
1448
                // Check to see if this token opened the scope.
1449
                if ($this->tokens[$i]['scope_opener'] === $i) {
×
1450
                    $stackPtr = $this->tokens[$i]['scope_condition'];
×
1451
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1452
                        $type = $this->tokens[$stackPtr]['type'];
×
NEW
1453
                        StatusWriter::write("=> Found scope opener for $stackPtr:$type", ($level + 1));
×
1454
                    }
1455

1456
                    $stackPtr = $this->tokens[$i]['scope_condition'];
×
1457

1458
                    // If we find a scope opener that has a shared closer,
1459
                    // then we need to go back over the condition map that we
1460
                    // just created and fix ourselves as we just added some
1461
                    // conditions where there was none. This happens for T_CASE
1462
                    // statements that are using the same break statement.
1463
                    if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
×
1464
                        // This opener shares its closer with the previous opener,
1465
                        // but we still need to check if the two openers share their
1466
                        // closer with each other directly (like CASE and DEFAULT)
1467
                        // or if they are just sharing because one doesn't have a
1468
                        // closer (like CASE with no BREAK using a SWITCHes closer).
1469
                        $thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
×
1470
                        $opener   = $this->tokens[$lastOpener]['scope_condition'];
×
1471

1472
                        $isShared = isset($this->scopeOpeners[$thisType]['with'][$this->tokens[$opener]['code']]);
×
1473

1474
                        reset($this->scopeOpeners[$thisType]['end']);
×
1475
                        reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
×
1476
                        $sameEnd = (current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']));
×
1477

1478
                        if ($isShared === true && $sameEnd === true) {
×
1479
                            $badToken = $opener;
×
1480
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1481
                                $type = $this->tokens[$badToken]['type'];
×
NEW
1482
                                StatusWriter::write("* shared closer, cleaning up $badToken:$type *", ($level + 1));
×
1483
                            }
1484

1485
                            for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++) {
×
1486
                                $oldConditions = $this->tokens[$x]['conditions'];
×
1487
                                $oldLevel      = $this->tokens[$x]['level'];
×
1488
                                $this->tokens[$x]['level']--;
×
1489
                                unset($this->tokens[$x]['conditions'][$badToken]);
×
1490
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1491
                                    $type     = $this->tokens[$x]['type'];
×
1492
                                    $oldConds = '';
×
1493
                                    foreach ($oldConditions as $condition) {
×
1494
                                        $oldConds .= Tokens::tokenName($condition).',';
×
1495
                                    }
1496

1497
                                    $oldConds = rtrim($oldConds, ',');
×
1498

1499
                                    $newConds = '';
×
1500
                                    foreach ($this->tokens[$x]['conditions'] as $condition) {
×
1501
                                        $newConds .= Tokens::tokenName($condition).',';
×
1502
                                    }
1503

1504
                                    $newConds = rtrim($newConds, ',');
×
1505

1506
                                    $newLevel = $this->tokens[$x]['level'];
×
NEW
1507
                                    StatusWriter::write("* cleaned $x:$type *", ($level + 1));
×
NEW
1508
                                    StatusWriter::write("=> level changed from $oldLevel to $newLevel", ($level + 2));
×
NEW
1509
                                    StatusWriter::write("=> conditions changed from $oldConds to $newConds", ($level + 2));
×
1510
                                }//end if
1511
                            }//end for
1512

1513
                            unset($conditions[$badToken]);
×
1514
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1515
                                $type = $this->tokens[$badToken]['type'];
×
NEW
1516
                                StatusWriter::write("* token $badToken:$type removed from conditions array *", ($level + 1));
×
1517
                            }
1518

1519
                            unset($openers[$lastOpener]);
×
1520

1521
                            $level--;
×
1522
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1523
                                StatusWriter::write('* level decreased *', ($level + 2));
×
1524
                            }
1525
                        }//end if
1526
                    }//end if
1527

1528
                    $level++;
×
1529
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1530
                        StatusWriter::write('* level increased *', ($level + 1));
×
1531
                    }
1532

1533
                    $conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
×
1534
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1535
                        $type = $this->tokens[$stackPtr]['type'];
×
NEW
1536
                        StatusWriter::write("* token $stackPtr:$type added to conditions array *", ($level + 1));
×
1537
                    }
1538

1539
                    $lastOpener = $this->tokens[$i]['scope_opener'];
×
1540
                    if ($lastOpener !== null) {
×
1541
                        $openers[$lastOpener] = $lastOpener;
×
1542
                    }
1543
                } else if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
×
1544
                    foreach (array_reverse($openers) as $opener) {
×
1545
                        if ($this->tokens[$opener]['scope_closer'] === $i) {
×
1546
                            $oldOpener = array_pop($openers);
×
1547
                            if (empty($openers) === false) {
×
1548
                                $lastOpener           = array_pop($openers);
×
1549
                                $openers[$lastOpener] = $lastOpener;
×
1550
                            } else {
1551
                                $lastOpener = null;
×
1552
                            }
1553

1554
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1555
                                $type = $this->tokens[$oldOpener]['type'];
×
NEW
1556
                                StatusWriter::write("=> Found scope closer for $oldOpener:$type", ($level + 1));
×
1557
                            }
1558

1559
                            $oldCondition = array_pop($conditions);
×
1560
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1561
                                StatusWriter::write('* token '.Tokens::tokenName($oldCondition).' removed from conditions array *', ($level + 1));
×
1562
                            }
1563

1564
                            // Make sure this closer actually belongs to us.
1565
                            // Either the condition also has to think this is the
1566
                            // closer, or it has to allow sharing with us.
1567
                            $condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
×
1568
                            if ($condition !== $oldCondition) {
×
1569
                                if (isset($this->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
×
1570
                                    $badToken = $this->tokens[$oldOpener]['scope_condition'];
×
1571

1572
                                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1573
                                        $type = Tokens::tokenName($oldCondition);
×
NEW
1574
                                        StatusWriter::write("* scope closer was bad, cleaning up $badToken:$type *", ($level + 1));
×
1575
                                    }
1576

1577
                                    for ($x = ($oldOpener + 1); $x <= $i; $x++) {
×
1578
                                        $oldConditions = $this->tokens[$x]['conditions'];
×
1579
                                        $oldLevel      = $this->tokens[$x]['level'];
×
1580
                                        $this->tokens[$x]['level']--;
×
1581
                                        unset($this->tokens[$x]['conditions'][$badToken]);
×
1582
                                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1583
                                            $type     = $this->tokens[$x]['type'];
×
1584
                                            $oldConds = '';
×
1585
                                            foreach ($oldConditions as $condition) {
×
1586
                                                $oldConds .= Tokens::tokenName($condition).',';
×
1587
                                            }
1588

1589
                                            $oldConds = rtrim($oldConds, ',');
×
1590

1591
                                            $newConds = '';
×
1592
                                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
1593
                                                $newConds .= Tokens::tokenName($condition).',';
×
1594
                                            }
1595

1596
                                            $newConds = rtrim($newConds, ',');
×
1597

1598
                                            $newLevel = $this->tokens[$x]['level'];
×
NEW
1599
                                            StatusWriter::write("* cleaned $x:$type *", ($level + 1));
×
NEW
1600
                                            StatusWriter::write("=> level changed from $oldLevel to $newLevel", ($level + 2));
×
NEW
1601
                                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", ($level + 2));
×
1602
                                        }//end if
1603
                                    }//end for
1604
                                }//end if
1605
                            }//end if
1606

1607
                            $level--;
×
1608
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1609
                                StatusWriter::write('* level decreased *', ($level + 2));
×
1610
                            }
1611

1612
                            $this->tokens[$i]['level']      = $level;
×
1613
                            $this->tokens[$i]['conditions'] = $conditions;
×
1614
                        }//end if
1615
                    }//end foreach
1616
                }//end if
1617
            }//end if
1618
        }//end for
1619

1620
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
NEW
1621
            StatusWriter::write('*** END LEVEL MAP ***', 1);
×
1622
        }
1623

1624
    }//end createLevelMap()
1625

1626

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