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

PHPCSStandards / PHP_CodeSniffer / 14540685504

18 Apr 2025 07:32PM UTC coverage: 78.182% (+0.03%) from 78.154%
14540685504

Pull #123

github

web-flow
Merge 9a0ee0c67 into aab9cd620
Pull Request #123: Improve handling of disable/enable/ignore directives

76 of 77 new or added lines in 3 files covered. (98.7%)

162 existing lines in 1 file now uncovered.

19508 of 24952 relevant lines covered (78.18%)

85.56 hits per line

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

59.59
/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\IgnoreList;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHP_CodeSniffer\Util\Writers\StatusWriter;
17

18
abstract class Tokenizer
19
{
20

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

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

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

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

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

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

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

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

77

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

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

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

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

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

108
    }//end __construct()
109

110

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

130
        return false;
×
131

132
    }//end isMinifiedContent()
133

134

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

144
    }//end getTokens()
145

146

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

156

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

164

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

182
        $checkEncoding = false;
381✔
183
        if (function_exists('iconv_strlen') === true) {
381✔
184
            $checkEncoding = true;
381✔
185
        }
186

187
        $checkAnnotations = $this->config->annotations;
381✔
188
        $encoding         = $this->config->encoding;
381✔
189
        $tabWidth         = $this->config->tabWidth;
381✔
190

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

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

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

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

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

245
            $this->tokens[$i]['length'] = $length;
381✔
246

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

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

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

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

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

294
                            if ($this->tokens[$prev]['code'] === T_WHITESPACE
126✔
295
                                || $this->tokens[$prev]['code'] === T_DOC_COMMENT_WHITESPACE
63✔
296
                                || ($this->tokens[$prev]['code'] === T_INLINE_HTML
105✔
297
                                && trim($this->tokens[$prev]['content']) === '')
126✔
298
                            ) {
299
                                continue;
126✔
300
                            }
301

302
                            $lineHasOtherTokens = true;
63✔
303

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

310
                            $lineHasOtherContent = true;
45✔
311
                            break;
45✔
312
                        }//end for
313

314
                        $changedLines = false;
276✔
315
                        for ($next = $i; $next < $this->numTokens; $next++) {
276✔
316
                            if ($changedLines === true) {
276✔
317
                                // Changed lines.
318
                                break;
252✔
319
                            }
320

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

328
                            if ($next === $i) {
276✔
329
                                continue;
276✔
330
                            }
331

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

340
                            $lineHasOtherTokens = true;
21✔
341

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

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

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

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

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

380
                        $disabledSniffs = [];
192✔
381

382
                        $additionalText = substr($commentText, 14);
192✔
383
                        if (empty($additionalText) === true) {
192✔
384
                            $ignoring = $ignoreAll;
96✔
385
                        } else {
386
                            if ($ignoring === null) {
96✔
387
                                $ignoring = IgnoreList::ignoringNone();
96✔
388
                            } else {
389
                                $ignoring = clone $ignoring;
21✔
390
                            }
391

392
                            $parts = explode(',', $additionalText);
96✔
393
                            foreach ($parts as $sniffCode) {
96✔
394
                                $sniffCode = trim($sniffCode);
96✔
395
                                $disabledSniffs[$sniffCode] = true;
96✔
396
                                $ignoring->set($sniffCode, true);
96✔
397
                            }
398
                        }
399

400
                        $this->tokens[$i]['code']       = T_PHPCS_DISABLE;
192✔
401
                        $this->tokens[$i]['type']       = 'T_PHPCS_DISABLE';
192✔
402
                        $this->tokens[$i]['sniffCodes'] = $disabledSniffs;
192✔
403
                    } else if (substr($commentTextLower, 0, 12) === 'phpcs:enable') {
207✔
404
                        if ($ignoring !== null) {
150✔
405
                            $enabledSniffs = [];
147✔
406

407
                            $additionalText = substr($commentText, 13);
147✔
408
                            if (empty($additionalText) === true) {
147✔
409
                                $ignoring = null;
99✔
410
                            } else {
411
                                $ignoring = clone $ignoring;
51✔
412
                                $parts    = explode(',', $additionalText);
51✔
413
                                foreach ($parts as $sniffCode) {
51✔
414
                                    $sniffCode = trim($sniffCode);
51✔
415
                                    $enabledSniffs[$sniffCode] = true;
51✔
416
                                    $ignoring->set($sniffCode, false);
51✔
417
                                }
418

419
                                if ($ignoring->isEmpty() === true) {
51✔
420
                                    $ignoring = null;
24✔
421
                                }
422
                            }
423

424
                            if ($lineHasOtherContent === false) {
147✔
425
                                // Completely ignore the comment line.
426
                                $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreAll;
132✔
427
                            } else {
428
                                // The comment is on the same line as the code it is ignoring,
429
                                // so respect the new ignore rules.
430
                                $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
15✔
431
                            }
432

433
                            $this->tokens[$i]['sniffCodes'] = $enabledSniffs;
147✔
434
                        }//end if
435

436
                        $this->tokens[$i]['code'] = T_PHPCS_ENABLE;
150✔
437
                        $this->tokens[$i]['type'] = 'T_PHPCS_ENABLE';
150✔
438
                    } else if (substr($commentTextLower, 0, 12) === 'phpcs:ignore') {
78✔
439
                        $ignoreRules = [];
78✔
440

441
                        $additionalText = substr($commentText, 13);
78✔
442
                        if (empty($additionalText) === true) {
78✔
443
                            $ignoreRules  = ['.all' => true];
45✔
444
                            $lineIgnoring = $ignoreAll;
45✔
445
                        } else {
446
                            $parts = explode(',', $additionalText);
33✔
447
                            if ($ignoring === null) {
33✔
448
                                $lineIgnoring = IgnoreList::ignoringNone();
21✔
449
                            } else {
450
                                $lineIgnoring = clone $ignoring;
12✔
451
                            }
452

453
                            foreach ($parts as $sniffCode) {
33✔
454
                                $ignoreRules[trim($sniffCode)] = true;
33✔
455
                                $lineIgnoring->set($sniffCode, true);
33✔
456
                            }
457
                        }
458

459
                        $this->tokens[$i]['code']       = T_PHPCS_IGNORE;
78✔
460
                        $this->tokens[$i]['type']       = 'T_PHPCS_IGNORE';
78✔
461
                        $this->tokens[$i]['sniffCodes'] = $ignoreRules;
78✔
462

463
                        if ($lineHasOtherContent === false) {
78✔
464
                            // Completely ignore the comment line, and set the following
465
                            // line to include the ignore rules we've set.
466
                            $this->ignoredLines[$this->tokens[$i]['line']]       = $ignoreAll;
51✔
467
                            $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $lineIgnoring;
51✔
468
                        } else {
469
                            // The comment is on the same line as the code it is ignoring,
470
                            // so respect the ignore rules it set.
471
                            $this->ignoredLines[$this->tokens[$i]['line']] = $lineIgnoring;
27✔
472
                        }
473
                    }//end if
474
                }//end if
475
            }//end if
476

477
            if ($ignoring !== null && isset($this->ignoredLines[$this->tokens[$i]['line']]) === false) {
381✔
478
                $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
192✔
479
            }
480
        }//end for
481

482
        // If annotations are being ignored, we clear out all the ignore rules
483
        // but leave the annotations tokenized as normal.
484
        if ($checkAnnotations === false) {
381✔
UNCOV
485
            $this->ignoredLines = [];
×
486
        }
487

488
    }//end createPositionMap()
123✔
489

490

491
    /**
492
     * Replaces tabs in original token content with spaces.
493
     *
494
     * Each tab can represent between 1 and $config->tabWidth spaces,
495
     * so this cannot be a straight string replace. The original content
496
     * is placed into an orig_content index and the new token length is also
497
     * set in the length index.
498
     *
499
     * @param array  $token    The token to replace tabs inside.
500
     * @param string $prefix   The character to use to represent the start of a tab.
501
     * @param string $padding  The character to use to represent the end of a tab.
502
     * @param int    $tabWidth The number of spaces each tab represents.
503
     *
504
     * @return void
505
     */
506
    public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth=null)
177✔
507
    {
508
        $checkEncoding = false;
177✔
509
        if (function_exists('iconv_strlen') === true) {
177✔
510
            $checkEncoding = true;
177✔
511
        }
512

513
        $currColumn = $token['column'];
177✔
514
        if ($tabWidth === null) {
177✔
515
            $tabWidth = $this->config->tabWidth;
177✔
516
            if ($tabWidth === 0) {
177✔
517
                $tabWidth = 1;
3✔
518
            }
519
        }
520

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

525
            $firstTabSize = ($tabWidth - (($currColumn - 1) % $tabWidth));
171✔
526
            $length       = ($firstTabSize + ($tabWidth * ($numTabs - 1)));
171✔
527
            $newContent   = $prefix.str_repeat($padding, ($length - 1));
171✔
528
        } else {
529
            // We need to determine the length of each tab.
530
            $tabs = explode("\t", $token['content']);
174✔
531

532
            $numTabs    = (count($tabs) - 1);
174✔
533
            $tabNum     = 0;
174✔
534
            $newContent = '';
174✔
535
            $length     = 0;
174✔
536

537
            foreach ($tabs as $content) {
174✔
538
                if ($content !== '') {
174✔
539
                    $newContent .= $content;
174✔
540
                    if ($checkEncoding === true) {
174✔
541
                        // Not using ASCII encoding, so take a bit more care.
542
                        $oldLevel = error_reporting();
174✔
543
                        error_reporting(0);
174✔
544
                        $contentLength = iconv_strlen($content, $this->config->encoding);
174✔
545
                        error_reporting($oldLevel);
174✔
546
                        if ($contentLength === false) {
174✔
547
                            // String contained invalid characters, so revert to default.
548
                            $contentLength = strlen($content);
117✔
549
                        }
550
                    } else {
UNCOV
551
                        $contentLength = strlen($content);
×
552
                    }
553

554
                    $currColumn += $contentLength;
174✔
555
                    $length     += $contentLength;
174✔
556
                }
557

558
                // The last piece of content does not have a tab after it.
559
                if ($tabNum === $numTabs) {
174✔
560
                    break;
174✔
561
                }
562

563
                // Process the tab that comes after the content.
564
                $tabNum++;
174✔
565

566
                // Move the pointer to the next tab stop.
567
                $pad         = ($tabWidth - ($currColumn + $tabWidth - 1) % $tabWidth);
174✔
568
                $currColumn += $pad;
174✔
569
                $length     += $pad;
174✔
570
                $newContent .= $prefix.str_repeat($padding, ($pad - 1));
174✔
571
            }//end foreach
572
        }//end if
573

574
        $token['orig_content'] = $token['content'];
177✔
575
        $token['content']      = $newContent;
177✔
576
        $token['length']       = $length;
177✔
577

578
    }//end replaceTabsInToken()
59✔
579

580

581
    /**
582
     * Creates a map of brackets positions.
583
     *
584
     * @return void
585
     */
586
    private function createTokenMap()
213✔
587
    {
588
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
213✔
UNCOV
589
            StatusWriter::write('*** START TOKEN MAP ***', 1);
×
590
        }
591

592
        $squareOpeners   = [];
213✔
593
        $curlyOpeners    = [];
213✔
594
        $this->numTokens = count($this->tokens);
213✔
595

596
        $openers = [];
213✔
597

598
        for ($i = 0; $i < $this->numTokens; $i++) {
213✔
599
            /*
600
                Parenthesis mapping.
601
            */
602

603
            if (isset(Tokens::PARENTHESIS_OPENERS[$this->tokens[$i]['code']]) === true) {
213✔
604
                // Find the next non-empty token.
605
                $find = Tokens::EMPTY_TOKENS;
213✔
606
                if ($this->tokens[$i]['code'] === T_FUNCTION) {
213✔
607
                    $find[T_STRING]      = T_STRING;
210✔
608
                    $find[T_BITWISE_AND] = T_BITWISE_AND;
210✔
609
                }
610

611
                for ($j = ($i + 1); isset($this->tokens[$j], $find[$this->tokens[$j]['code']]) === true; $j++);
213✔
612
                if ($j < $this->numTokens && $this->tokens[$j]['code'] === T_OPEN_PARENTHESIS) {
213✔
613
                    $openers[] = $j;
210✔
614
                    $this->tokens[$i]['parenthesis_opener'] = $j;
210✔
615
                    $this->tokens[$i]['parenthesis_closer'] = null;
210✔
616
                    $this->tokens[$i]['parenthesis_owner']  = $i;
210✔
617

618
                    $this->tokens[$j]['parenthesis_opener'] = $j;
210✔
619
                    $this->tokens[$j]['parenthesis_owner']  = $i;
210✔
620

621
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
UNCOV
622
                        StatusWriter::write("=> Found parenthesis owner at $i", (count($openers) + 1));
×
UNCOV
623
                        StatusWriter::write("=> Found parenthesis opener at $j for $i", count($openers));
×
624
                    }
625

626
                    $i = $j;
212✔
627
                }
628
            } else if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
213✔
629
                $openers[] = $i;
210✔
630
                $this->tokens[$i]['parenthesis_opener'] = $i;
210✔
631

632
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
633
                    StatusWriter::write("=> Found unowned parenthesis opener at $i", count($openers));
140✔
634
                }
635
            } else if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
213✔
636
                // Did we set an owner for this set of parenthesis?
637
                $numOpeners = count($openers);
210✔
638
                if ($numOpeners !== 0) {
210✔
639
                    $opener = array_pop($openers);
210✔
640
                    if (isset($this->tokens[$opener]['parenthesis_owner']) === true) {
210✔
641
                        $owner = $this->tokens[$opener]['parenthesis_owner'];
210✔
642

643
                        $this->tokens[$owner]['parenthesis_closer'] = $i;
210✔
644
                        $this->tokens[$i]['parenthesis_owner']      = $owner;
210✔
645

646
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
647
                            StatusWriter::write("=> Found parenthesis closer at $i for $owner", (count($openers) + 1));
140✔
648
                        }
649
                    } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
650
                        StatusWriter::write("=> Found unowned parenthesis closer at $i for $opener", (count($openers) + 1));
×
651
                    }
652

653
                    $this->tokens[$i]['parenthesis_opener']      = $opener;
210✔
654
                    $this->tokens[$i]['parenthesis_closer']      = $i;
210✔
655
                    $this->tokens[$opener]['parenthesis_closer'] = $i;
210✔
656
                }//end if
657
            } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE) {
213✔
658
                $openers[] = $i;
×
659
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
660
                    StatusWriter::write("=> Found attribute opener at $i", count($openers));
×
661
                }
662

UNCOV
663
                $this->tokens[$i]['attribute_opener'] = $i;
×
664
                $this->tokens[$i]['attribute_closer'] = null;
×
665
            } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE_END) {
213✔
UNCOV
666
                $numOpeners = count($openers);
×
UNCOV
667
                if ($numOpeners !== 0) {
×
668
                    $opener = array_pop($openers);
×
669
                    if (isset($this->tokens[$opener]['attribute_opener']) === true) {
×
670
                        $this->tokens[$opener]['attribute_closer'] = $i;
×
671

UNCOV
672
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
673
                            StatusWriter::write("=> Found attribute closer at $i for $opener", (count($openers) + 1));
×
674
                        }
675

676
                        for ($x = ($opener + 1); $x <= $i; ++$x) {
×
677
                            if (isset($this->tokens[$x]['attribute_closer']) === true) {
×
UNCOV
678
                                continue;
×
679
                            }
680

UNCOV
681
                            $this->tokens[$x]['attribute_opener'] = $opener;
×
UNCOV
682
                            $this->tokens[$x]['attribute_closer'] = $i;
×
683
                        }
UNCOV
684
                    } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
685
                        StatusWriter::write("=> Found unowned attribute closer at $i for $opener", (count($openers) + 1));
×
686
                    }
687
                }//end if
688
            }//end if
689

690
            /*
691
                Bracket mapping.
692
            */
693

694
            switch ($this->tokens[$i]['code']) {
213✔
695
            case T_OPEN_SQUARE_BRACKET:
213✔
696
                $squareOpeners[] = $i;
57✔
697

698
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
699
                    StatusWriter::write("=> Found square bracket opener at $i", (count($squareOpeners) + count($curlyOpeners)));
×
700
                }
701
                break;
57✔
702
            case T_OPEN_CURLY_BRACKET:
213✔
703
                if (isset($this->tokens[$i]['scope_closer']) === false) {
210✔
704
                    $curlyOpeners[] = $i;
210✔
705

706
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
UNCOV
707
                        StatusWriter::write("=> Found curly bracket opener at $i", (count($squareOpeners) + count($curlyOpeners)));
×
708
                    }
709
                }
710
                break;
210✔
711
            case T_CLOSE_SQUARE_BRACKET:
213✔
712
                if (empty($squareOpeners) === false) {
57✔
713
                    $opener = array_pop($squareOpeners);
57✔
714
                    $this->tokens[$i]['bracket_opener']      = $opener;
57✔
715
                    $this->tokens[$i]['bracket_closer']      = $i;
57✔
716
                    $this->tokens[$opener]['bracket_opener'] = $opener;
57✔
717
                    $this->tokens[$opener]['bracket_closer'] = $i;
57✔
718

719
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
UNCOV
720
                        StatusWriter::write("=> Found square bracket closer at $i for $opener", (count($squareOpeners) + count($curlyOpeners) + 1));
×
721
                    }
722
                }
723
                break;
57✔
724
            case T_CLOSE_CURLY_BRACKET:
213✔
725
                if (empty($curlyOpeners) === false
210✔
726
                    && isset($this->tokens[$i]['scope_opener']) === false
210✔
727
                ) {
728
                    $opener = array_pop($curlyOpeners);
210✔
729
                    $this->tokens[$i]['bracket_opener']      = $opener;
210✔
730
                    $this->tokens[$i]['bracket_closer']      = $i;
210✔
731
                    $this->tokens[$opener]['bracket_opener'] = $opener;
210✔
732
                    $this->tokens[$opener]['bracket_closer'] = $i;
210✔
733

734
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
UNCOV
735
                        StatusWriter::write("=> Found curly bracket closer at $i for $opener", (count($squareOpeners) + count($curlyOpeners) + 1));
×
736
                    }
737
                }
738
                break;
210✔
739
            default:
740
                continue 2;
213✔
741
            }//end switch
742
        }//end for
743

744
        // Cleanup for any openers that we didn't find closers for.
745
        // This typically means there was a syntax error breaking things.
746
        foreach ($openers as $opener) {
213✔
UNCOV
747
            unset($this->tokens[$opener]['parenthesis_opener']);
×
UNCOV
748
            unset($this->tokens[$opener]['parenthesis_owner']);
×
749
        }
750

751
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
213✔
UNCOV
752
            StatusWriter::write('*** END TOKEN MAP ***', 1);
×
753
        }
754

755
    }//end createTokenMap()
71✔
756

757

758
    /**
759
     * Creates a map for the parenthesis tokens that surround other tokens.
760
     *
761
     * @return void
762
     */
763
    private function createParenthesisNestingMap()
204✔
764
    {
765
        $map = [];
204✔
766
        for ($i = 0; $i < $this->numTokens; $i++) {
204✔
767
            if (isset($this->tokens[$i]['parenthesis_opener']) === true
204✔
768
                && $i === $this->tokens[$i]['parenthesis_opener']
204✔
769
            ) {
770
                if (empty($map) === false) {
204✔
771
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
772
                }
773

774
                if (isset($this->tokens[$i]['parenthesis_closer']) === true) {
204✔
775
                    $map[$this->tokens[$i]['parenthesis_opener']]
204✔
776
                        = $this->tokens[$i]['parenthesis_closer'];
204✔
777
                }
778
            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
204✔
779
                && $i === $this->tokens[$i]['parenthesis_closer']
204✔
780
            ) {
781
                array_pop($map);
204✔
782
                if (empty($map) === false) {
204✔
783
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
784
                }
785
            } else {
786
                if (empty($map) === false) {
204✔
787
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
788
                }
789
            }//end if
790
        }//end for
791

792
    }//end createParenthesisNestingMap()
68✔
793

794

795
    /**
796
     * Creates a scope map of tokens that open scopes.
797
     *
798
     * @return void
799
     * @see    recurseScopeMap()
800
     */
801
    private function createScopeMap()
×
802
    {
803
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
804
            StatusWriter::write('*** START SCOPE MAP ***', 1);
×
805
        }
806

UNCOV
807
        for ($i = 0; $i < $this->numTokens; $i++) {
×
808
            // Check to see if the current token starts a new scope.
809
            if (isset($this->scopeOpeners[$this->tokens[$i]['code']]) === true) {
×
810
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
811
                    $type    = $this->tokens[$i]['type'];
×
UNCOV
812
                    $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
813
                    StatusWriter::write("Start scope map at $i:$type => $content", 1);
×
814
                }
815

816
                if (isset($this->tokens[$i]['scope_condition']) === true) {
×
UNCOV
817
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
818
                        StatusWriter::write('* already processed, skipping *', 1);
×
819
                    }
820

821
                    continue;
×
822
                }
823

UNCOV
824
                $i = $this->recurseScopeMap($i);
×
825
            }//end if
826
        }//end for
827

UNCOV
828
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
829
            StatusWriter::write('*** END SCOPE MAP ***', 1);
×
830
        }
831

832
    }//end createScopeMap()
833

834

835
    /**
836
     * Recurses though the scope openers to build a scope map.
837
     *
838
     * @param int $stackPtr The position in the stack of the token that
839
     *                      opened the scope (eg. an IF token or FOR token).
840
     * @param int $depth    How many scope levels down we are.
841
     * @param int $ignore   How many curly braces we are ignoring.
842
     *
843
     * @return int The position in the stack that closed the scope.
844
     * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the nesting level gets too deep.
845
     */
846
    private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
222✔
847
    {
848
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
UNCOV
849
            StatusWriter::write("=> Begin scope map recursion at token $stackPtr with depth $depth", $depth);
×
850
        }
851

852
        $opener    = null;
222✔
853
        $currType  = $this->tokens[$stackPtr]['code'];
222✔
854
        $startLine = $this->tokens[$stackPtr]['line'];
222✔
855

856
        // We will need this to restore the value if we end up
857
        // returning a token ID that causes our calling function to go back
858
        // over already ignored braces.
859
        $originalIgnore = $ignore;
222✔
860

861
        // If the start token for this scope opener is the same as
862
        // the scope token, we have already found our opener.
863
        if (isset($this->scopeOpeners[$currType]['start'][$currType]) === true) {
222✔
864
            $opener = $stackPtr;
×
865
        }
866

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

870
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
UNCOV
871
                $type    = $this->tokens[$i]['type'];
×
872
                $line    = $this->tokens[$i]['line'];
×
873
                $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
874

UNCOV
875
                $statusMessage = "Process token $i on line $line [";
×
876
                if ($opener !== null) {
×
877
                    $statusMessage .= "opener:$opener;";
×
878
                }
879

UNCOV
880
                if ($ignore > 0) {
×
UNCOV
881
                    $statusMessage .= "ignore=$ignore;";
×
882
                }
883

UNCOV
884
                $statusMessage .= "]: $type => $content";
×
UNCOV
885
                StatusWriter::write($statusMessage, $depth);
×
886
            }//end if
887

888
            // Very special case for IF statements in PHP that can be defined without
889
            // scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1;
890
            // If an IF statement below this one has an opener but no
891
            // keyword, the opener will be incorrectly assigned to this IF statement.
892
            // The same case also applies to USE statements, which don't have to have
893
            // openers, so a following USE statement can cause an incorrect brace match.
894
            if (($currType === T_IF || $currType === T_ELSE || $currType === T_USE)
222✔
895
                && $opener === null
222✔
896
                && ($this->tokens[$i]['code'] === T_SEMICOLON
210✔
897
                || $this->tokens[$i]['code'] === T_CLOSE_TAG)
222✔
898
            ) {
899
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
72✔
UNCOV
900
                    $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
901
                    if ($this->tokens[$i]['code'] === T_SEMICOLON) {
×
UNCOV
902
                        $closerType = 'semicolon';
×
903
                    } else {
UNCOV
904
                        $closerType = 'close tag';
×
905
                    }
906

UNCOV
907
                    StatusWriter::write("=> Found $closerType before scope opener for $stackPtr:$type, bailing", $depth);
×
908
                }
909

910
                return $i;
72✔
911
            }
912

913
            // Special case for PHP control structures that have no braces.
914
            // If we find a curly brace closer before we find the opener,
915
            // we're not going to find an opener. That closer probably belongs to
916
            // a control structure higher up.
917
            if ($opener === null
222✔
918
                && $ignore === 0
222✔
919
                && $tokenType === T_CLOSE_CURLY_BRACKET
222✔
920
                && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
222✔
921
            ) {
UNCOV
922
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
923
                    $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
924
                    StatusWriter::write("=> Found curly brace closer before scope opener for $stackPtr:$type, bailing", $depth);
×
925
                }
926

UNCOV
927
                return ($i - 1);
×
928
            }
929

930
            if ($opener !== null
222✔
931
                && (isset($this->tokens[$i]['scope_opener']) === false
222✔
932
                || $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true)
222✔
933
                && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
222✔
934
            ) {
935
                if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
222✔
936
                    // The last opening bracket must have been for a string
937
                    // offset or alike, so let's ignore it.
938
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
UNCOV
939
                        StatusWriter::write('* finished ignoring curly brace *', $depth);
×
940
                    }
941

942
                    $ignore--;
183✔
943
                    continue;
183✔
944
                } else if ($this->tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET
222✔
945
                    && $tokenType !== T_CLOSE_CURLY_BRACKET
222✔
946
                ) {
947
                    // The opener is a curly bracket so the closer must be a curly bracket as well.
948
                    // We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered
949
                    // a closer of T_IF when it should not.
UNCOV
950
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
951
                        $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
952
                        StatusWriter::write("=> Ignoring non-curly scope closer for $stackPtr:$type", $depth);
×
953
                    }
954
                } else {
955
                    $scopeCloser = $i;
222✔
956
                    $todo        = [
148✔
957
                        $stackPtr,
222✔
958
                        $opener,
222✔
959
                    ];
148✔
960

961
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
UNCOV
962
                        $type       = $this->tokens[$stackPtr]['type'];
×
UNCOV
963
                        $closerType = $this->tokens[$scopeCloser]['type'];
×
UNCOV
964
                        StatusWriter::write("=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type", $depth);
×
965
                    }
966

967
                    $validCloser = true;
222✔
968
                    if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF)
222✔
969
                        && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)
222✔
970
                    ) {
971
                        // To be a closer, this token must have an opener.
972
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
973
                            StatusWriter::write('* closer needs to be tested *', $depth);
×
974
                        }
975

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

978
                        if (isset($this->tokens[$scopeCloser]['scope_opener']) === false) {
3✔
979
                            $validCloser = false;
×
980
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
981
                                StatusWriter::write('* closer is not valid (no opener found) *', $depth);
×
982
                            }
983
                        } else if ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
3✔
UNCOV
984
                            $validCloser = false;
×
UNCOV
985
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
986
                                $type       = $this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['type'];
×
UNCOV
987
                                $openerType = $this->tokens[$opener]['type'];
×
UNCOV
988
                                StatusWriter::write("* closer is not valid (mismatched opener type; $type != $openerType) *", $depth);
×
989
                            }
990
                        } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
991
                            StatusWriter::write('* closer was valid *', $depth);
2✔
992
                        }
993
                    } else {
994
                        // The closer was not processed, so we need to
995
                        // complete that token as well.
996
                        $todo[] = $scopeCloser;
222✔
997
                    }//end if
998

999
                    if ($validCloser === true) {
222✔
1000
                        foreach ($todo as $token) {
222✔
1001
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
222✔
1002
                            $this->tokens[$token]['scope_opener']    = $opener;
222✔
1003
                            $this->tokens[$token]['scope_closer']    = $scopeCloser;
222✔
1004
                        }
1005

1006
                        if ($this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true) {
222✔
1007
                            // As we are going back to where we started originally, restore
1008
                            // the ignore value back to its original value.
1009
                            $ignore = $originalIgnore;
201✔
1010
                            return $opener;
201✔
1011
                        } else if ($scopeCloser === $i
222✔
1012
                            && isset($this->scopeOpeners[$tokenType]) === true
222✔
1013
                        ) {
1014
                            // Unset scope_condition here or else the token will appear to have
1015
                            // already been processed, and it will be skipped. Normally we want that,
1016
                            // but in this case, the token is both a closer and an opener, so
1017
                            // it needs to act like an opener. This is also why we return the
1018
                            // token before this one; so the closer has a chance to be processed
1019
                            // a second time, but as an opener.
UNCOV
1020
                            unset($this->tokens[$scopeCloser]['scope_condition']);
×
UNCOV
1021
                            return ($i - 1);
×
1022
                        } else {
1023
                            return $i;
222✔
1024
                        }
1025
                    } else {
UNCOV
1026
                        continue;
×
1027
                    }//end if
1028
                }//end if
1029
            }//end if
1030

1031
            // Is this an opening condition ?
1032
            if (isset($this->scopeOpeners[$tokenType]) === true) {
222✔
1033
                if ($opener === null) {
204✔
1034
                    if ($tokenType === T_USE) {
18✔
1035
                        // PHP use keywords are special because they can be
1036
                        // used as blocks but also inline in function definitions.
1037
                        // So if we find them nested inside another opener, just skip them.
1038
                        continue;
×
1039
                    }
1040

1041
                    if ($tokenType === T_FUNCTION
18✔
1042
                        && $this->tokens[$stackPtr]['code'] !== T_FUNCTION
18✔
1043
                    ) {
1044
                        // Probably a closure, so process it manually.
1045
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
UNCOV
1046
                            $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1047
                            StatusWriter::write("=> Found function before scope opener for $stackPtr:$type, processing manually", $depth);
×
1048
                        }
1049

1050
                        if (isset($this->tokens[$i]['scope_closer']) === true) {
18✔
1051
                            // We've already processed this closure.
UNCOV
1052
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1053
                                StatusWriter::write('* already processed, skipping *', $depth);
×
1054
                            }
1055

1056
                            $i = $this->tokens[$i]['scope_closer'];
×
UNCOV
1057
                            continue;
×
1058
                        }
1059

1060
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
18✔
1061
                        continue;
18✔
1062
                    }//end if
1063

1064
                    if ($tokenType === T_CLASS) {
×
1065
                        // Probably an anonymous class inside another anonymous class,
1066
                        // so process it manually.
1067
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1068
                            $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1069
                            StatusWriter::write("=> Found class before scope opener for $stackPtr:$type, processing manually", $depth);
×
1070
                        }
1071

UNCOV
1072
                        if (isset($this->tokens[$i]['scope_closer']) === true) {
×
1073
                            // We've already processed this anon class.
1074
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1075
                                StatusWriter::write('* already processed, skipping *', $depth);
×
1076
                            }
1077

UNCOV
1078
                            $i = $this->tokens[$i]['scope_closer'];
×
UNCOV
1079
                            continue;
×
1080
                        }
1081

1082
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
×
UNCOV
1083
                        continue;
×
1084
                    }//end if
1085

1086
                    // Found another opening condition but still haven't
1087
                    // found our opener, so we are never going to find one.
1088
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1089
                        $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1090
                        StatusWriter::write("=> Found new opening condition before scope opener for $stackPtr:$type, ", $depth, 0);
×
1091
                    }
1092

UNCOV
1093
                    if (($this->tokens[$stackPtr]['code'] === T_IF
×
UNCOV
1094
                        || $this->tokens[$stackPtr]['code'] === T_ELSEIF
×
1095
                        || $this->tokens[$stackPtr]['code'] === T_ELSE)
×
UNCOV
1096
                        && ($this->tokens[$i]['code'] === T_ELSE
×
1097
                        || $this->tokens[$i]['code'] === T_ELSEIF)
×
1098
                    ) {
UNCOV
1099
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1100
                            StatusWriter::write('continuing');
×
1101
                        }
1102

UNCOV
1103
                        return ($i - 1);
×
1104
                    } else {
UNCOV
1105
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1106
                            StatusWriter::write('backtracking');
×
1107
                        }
1108

UNCOV
1109
                        return $stackPtr;
×
1110
                    }
1111
                }//end if
1112

1113
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
204✔
1114
                    StatusWriter::write('* token is an opening condition *', $depth);
×
1115
                }
1116

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

1119
                if (isset($this->tokens[$i]['scope_condition']) === true) {
204✔
1120
                    // We've been here before.
1121
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
UNCOV
1122
                        StatusWriter::write('* already processed, skipping *', $depth);
×
1123
                    }
1124

1125
                    if ($isShared === false
183✔
1126
                        && isset($this->tokens[$i]['scope_closer']) === true
183✔
1127
                    ) {
1128
                        $i = $this->tokens[$i]['scope_closer'];
183✔
1129
                    }
1130

1131
                    continue;
183✔
1132
                } else if ($currType === $tokenType
204✔
1133
                    && $isShared === false
204✔
1134
                    && $opener === null
204✔
1135
                ) {
1136
                    // We haven't yet found our opener, but we have found another
1137
                    // scope opener which is the same type as us, and we don't
1138
                    // share openers, so we will never find one.
UNCOV
1139
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1140
                        StatusWriter::write('* it was another token\'s opener, bailing *', $depth);
×
1141
                    }
1142

UNCOV
1143
                    return $stackPtr;
×
1144
                } else {
1145
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
204✔
UNCOV
1146
                        StatusWriter::write('* searching for opener *', $depth);
×
1147
                    }
1148

1149
                    if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
204✔
1150
                        $oldIgnore = $ignore;
186✔
1151
                        $ignore    = 0;
186✔
1152
                    }
1153

1154
                    // PHP has a max nesting level for functions. Stop before we hit that limit
1155
                    // because too many loops means we've run into trouble anyway.
1156
                    if ($depth > 50) {
204✔
UNCOV
1157
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1158
                            StatusWriter::write('* reached maximum nesting level; aborting *', $depth);
×
1159
                        }
1160

UNCOV
1161
                        throw new TokenizerException('Maximum nesting level reached; file could not be processed');
×
1162
                    }
1163

1164
                    $oldDepth = $depth;
204✔
1165
                    if ($isShared === true
204✔
1166
                        && isset($this->scopeOpeners[$tokenType]['with'][$currType]) === true
204✔
1167
                    ) {
1168
                        // Don't allow the depth to increment because this is
1169
                        // possibly not a true nesting if we are sharing our closer.
1170
                        // This can happen, for example, when a SWITCH has a large
1171
                        // number of CASE statements with the same shared BREAK.
1172
                        $depth--;
204✔
1173
                    }
1174

1175
                    $i     = self::recurseScopeMap($i, ($depth + 1), $ignore);
204✔
1176
                    $depth = $oldDepth;
204✔
1177

1178
                    if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
204✔
1179
                        $ignore = $oldIgnore;
186✔
1180
                    }
1181
                }//end if
1182
            }//end if
1183

1184
            if (isset($this->scopeOpeners[$currType]['start'][$tokenType]) === true
222✔
1185
                && $opener === null
222✔
1186
            ) {
1187
                if ($tokenType === T_OPEN_CURLY_BRACKET) {
222✔
1188
                    if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true
219✔
1189
                        && $i < $this->tokens[$stackPtr]['parenthesis_closer']
219✔
1190
                    ) {
1191
                        // We found a curly brace inside the condition of the
1192
                        // current scope opener, so it must be a string offset.
1193
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
UNCOV
1194
                            StatusWriter::write('* ignoring curly brace inside condition *', $depth);
×
1195
                        }
1196

1197
                        $ignore++;
18✔
1198
                    } else {
1199
                        // Make sure this is actually an opener and not a
1200
                        // string offset (e.g., $var{0}).
1201
                        for ($x = ($i - 1); $x > 0; $x--) {
219✔
1202
                            if (isset(Tokens::EMPTY_TOKENS[$this->tokens[$x]['code']]) === true) {
219✔
1203
                                continue;
219✔
1204
                            } else {
1205
                                // If the first non-whitespace/comment token looks like this
1206
                                // brace is a string offset, or this brace is mid-way through
1207
                                // a new statement, it isn't a scope opener.
1208
                                $disallowed  = Tokens::ASSIGNMENT_TOKENS;
219✔
1209
                                $disallowed += [
146✔
1210
                                    T_DOLLAR                   => true,
219✔
1211
                                    T_VARIABLE                 => true,
219✔
1212
                                    T_OBJECT_OPERATOR          => true,
219✔
1213
                                    T_NULLSAFE_OBJECT_OPERATOR => true,
219✔
1214
                                    T_COMMA                    => true,
219✔
1215
                                    T_OPEN_PARENTHESIS         => true,
219✔
1216
                                ];
146✔
1217

1218
                                if (isset($disallowed[$this->tokens[$x]['code']]) === true) {
219✔
UNCOV
1219
                                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1220
                                        StatusWriter::write('* ignoring curly brace *', $depth);
×
1221
                                    }
1222

UNCOV
1223
                                    $ignore++;
×
1224
                                }
1225

1226
                                break;
219✔
1227
                            }//end if
1228
                        }//end for
1229
                    }//end if
1230
                }//end if
1231

1232
                if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
222✔
1233
                    $openerNested = isset($this->tokens[$i]['nested_parenthesis']);
222✔
1234
                    $ownerNested  = isset($this->tokens[$stackPtr]['nested_parenthesis']);
222✔
1235

1236
                    if (($openerNested === true && $ownerNested === false)
222✔
1237
                        || ($openerNested === false && $ownerNested === true)
222✔
1238
                        || ($openerNested === true
222✔
1239
                        && $this->tokens[$i]['nested_parenthesis'] !== $this->tokens[$stackPtr]['nested_parenthesis'])
222✔
1240
                    ) {
1241
                        // We found the a token that looks like the opener, but it's nested differently.
1242
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
UNCOV
1243
                            $type = $this->tokens[$i]['type'];
×
1244
                            StatusWriter::write("* ignoring possible opener $i:$type as nested parenthesis don't match *", $depth);
12✔
1245
                        }
1246
                    } else {
1247
                        // We found the opening scope token for $currType.
1248
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
UNCOV
1249
                            $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1250
                            StatusWriter::write("=> Found scope opener for $stackPtr:$type", $depth);
×
1251
                        }
1252

1253
                        $opener = $i;
222✔
1254
                    }
1255
                }//end if
1256
            } else if ($tokenType === T_SEMICOLON
222✔
1257
                && $opener === null
222✔
1258
                && (isset($this->tokens[$stackPtr]['parenthesis_closer']) === false
209✔
1259
                || $i > $this->tokens[$stackPtr]['parenthesis_closer'])
222✔
1260
            ) {
1261
                // Found the end of a statement but still haven't
1262
                // found our opener, so we are never going to find one.
1263
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
UNCOV
1264
                    $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1265
                    StatusWriter::write("=> Found end of statement before scope opener for $stackPtr:$type, continuing", $depth);
×
1266
                }
1267

1268
                return ($i - 1);
183✔
1269
            } else if ($tokenType === T_OPEN_PARENTHESIS) {
222✔
1270
                if (isset($this->tokens[$i]['parenthesis_owner']) === true) {
222✔
1271
                    $owner = $this->tokens[$i]['parenthesis_owner'];
222✔
1272
                    if (isset(Tokens::SCOPE_OPENERS[$this->tokens[$owner]['code']]) === true
222✔
1273
                        && isset($this->tokens[$i]['parenthesis_closer']) === true
222✔
1274
                    ) {
1275
                        // If we get into here, then we opened a parenthesis for
1276
                        // a scope (eg. an if or else if) so we need to update the
1277
                        // start of the line so that when we check to see
1278
                        // if the closing parenthesis is more than n lines away from
1279
                        // the statement, we check from the closing parenthesis.
1280
                        $startLine = $this->tokens[$this->tokens[$i]['parenthesis_closer']]['line'];
222✔
1281
                    }
1282
                }
1283
            } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
222✔
1284
                // We opened something that we don't have a scope opener for.
1285
                // Examples of this are curly brackets for string offsets etc.
1286
                // We want to ignore this so that we don't have an invalid scope
1287
                // map.
1288
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
183✔
UNCOV
1289
                    StatusWriter::write('* ignoring curly brace *', $depth);
×
1290
                }
1291

1292
                $ignore++;
183✔
1293
            } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
222✔
1294
                // We found the end token for the opener we were ignoring.
1295
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
18✔
UNCOV
1296
                    StatusWriter::write('* finished ignoring curly brace *', $depth);
×
1297
                }
1298

1299
                $ignore--;
18✔
1300
            } else if ($opener === null
222✔
1301
                && isset($this->scopeOpeners[$currType]) === true
222✔
1302
            ) {
1303
                // If we still haven't found the opener after 30 lines,
1304
                // we're not going to find it, unless we know it requires
1305
                // an opener (in which case we better keep looking) or the last
1306
                // token was empty (in which case we'll just confirm there is
1307
                // more code in this file and not just a big comment).
1308
                if ($this->tokens[$i]['line'] >= ($startLine + 30)
222✔
1309
                    && isset(Tokens::EMPTY_TOKENS[$this->tokens[($i - 1)]['code']]) === false
222✔
1310
                ) {
1311
                    if ($this->scopeOpeners[$currType]['strict'] === true) {
×
1312
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1313
                            $type  = $this->tokens[$stackPtr]['type'];
×
UNCOV
1314
                            $lines = ($this->tokens[$i]['line'] - $startLine);
×
UNCOV
1315
                            StatusWriter::write("=> Still looking for $stackPtr:$type scope opener after $lines lines", $depth);
×
1316
                        }
1317
                    } else {
UNCOV
1318
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1319
                            $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1320
                            StatusWriter::write("=> Couldn't find scope opener for $stackPtr:$type, bailing", $depth);
×
1321
                        }
1322

1323
                        return $stackPtr;
148✔
1324
                    }
1325
                }
1326
            } else if ($opener !== null
204✔
1327
                && $tokenType !== T_BREAK
204✔
1328
                && isset($this->endScopeTokens[$tokenType]) === true
204✔
1329
            ) {
1330
                if (isset($this->tokens[$i]['scope_condition']) === false) {
186✔
1331
                    if ($ignore > 0) {
186✔
1332
                        // We found the end token for the opener we were ignoring.
UNCOV
1333
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1334
                            StatusWriter::write('* finished ignoring curly brace *', $depth);
×
1335
                        }
1336

1337
                        $ignore--;
×
1338
                    } else {
1339
                        // We found a token that closes the scope but it doesn't
1340
                        // have a condition, so it belongs to another token and
1341
                        // our token doesn't have a closer, so pretend this is
1342
                        // the closer.
1343
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
186✔
UNCOV
1344
                            $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1345
                            StatusWriter::write("=> Found (unexpected) scope closer for $stackPtr:$type", $depth);
×
1346
                        }
1347

1348
                        foreach ([$stackPtr, $opener] as $token) {
186✔
1349
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
186✔
1350
                            $this->tokens[$token]['scope_opener']    = $opener;
186✔
1351
                            $this->tokens[$token]['scope_closer']    = $i;
186✔
1352
                        }
1353

1354
                        return ($i - 1);
186✔
1355
                    }//end if
1356
                }//end if
1357
            }//end if
1358
        }//end for
1359

UNCOV
1360
        return $stackPtr;
×
1361

1362
    }//end recurseScopeMap()
1363

1364

1365
    /**
1366
     * Constructs the level map.
1367
     *
1368
     * The level map adds a 'level' index to each token which indicates the
1369
     * depth that a token within a set of scope blocks. It also adds a
1370
     * 'conditions' index which is an array of the scope conditions that opened
1371
     * each of the scopes - position 0 being the first scope opener.
1372
     *
1373
     * @return void
1374
     */
1375
    private function createLevelMap()
×
1376
    {
1377
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1378
            StatusWriter::write('*** START LEVEL MAP ***', 1);
×
1379
        }
1380

1381
        $this->numTokens = count($this->tokens);
×
1382
        $level           = 0;
×
1383
        $conditions      = [];
×
1384
        $lastOpener      = null;
×
UNCOV
1385
        $openers         = [];
×
1386

UNCOV
1387
        for ($i = 0; $i < $this->numTokens; $i++) {
×
1388
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1389
                $type = $this->tokens[$i]['type'];
×
1390
                $line = $this->tokens[$i]['line'];
×
1391
                $len  = $this->tokens[$i]['length'];
×
1392
                $col  = $this->tokens[$i]['column'];
×
1393

UNCOV
1394
                $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
1395

UNCOV
1396
                $statusMessage = "Process token $i on line $line [col:$col;len:$len;lvl:$level;";
×
UNCOV
1397
                if (empty($conditions) !== true) {
×
1398
                    $conditionString = 'conds;';
×
1399
                    foreach ($conditions as $condition) {
×
UNCOV
1400
                        $conditionString .= Tokens::tokenName($condition).',';
×
1401
                    }
1402

1403
                    $statusMessage .= rtrim($conditionString, ',').';';
×
1404
                }
1405

UNCOV
1406
                $statusMessage .= "]: $type => $content";
×
1407
                StatusWriter::write($statusMessage, ($level + 1));
×
1408
            }//end if
1409

1410
            $this->tokens[$i]['level']      = $level;
×
1411
            $this->tokens[$i]['conditions'] = $conditions;
×
1412

UNCOV
1413
            if (isset($this->tokens[$i]['scope_condition']) === true) {
×
1414
                // Check to see if this token opened the scope.
UNCOV
1415
                if ($this->tokens[$i]['scope_opener'] === $i) {
×
UNCOV
1416
                    $stackPtr = $this->tokens[$i]['scope_condition'];
×
UNCOV
1417
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1418
                        $type = $this->tokens[$stackPtr]['type'];
×
UNCOV
1419
                        StatusWriter::write("=> Found scope opener for $stackPtr:$type", ($level + 1));
×
1420
                    }
1421

UNCOV
1422
                    $stackPtr = $this->tokens[$i]['scope_condition'];
×
1423

1424
                    // If we find a scope opener that has a shared closer,
1425
                    // then we need to go back over the condition map that we
1426
                    // just created and fix ourselves as we just added some
1427
                    // conditions where there was none. This happens for T_CASE
1428
                    // statements that are using the same break statement.
UNCOV
1429
                    if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
×
1430
                        // This opener shares its closer with the previous opener,
1431
                        // but we still need to check if the two openers share their
1432
                        // closer with each other directly (like CASE and DEFAULT)
1433
                        // or if they are just sharing because one doesn't have a
1434
                        // closer (like CASE with no BREAK using a SWITCHes closer).
UNCOV
1435
                        $thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
×
1436
                        $opener   = $this->tokens[$lastOpener]['scope_condition'];
×
1437

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

1440
                        reset($this->scopeOpeners[$thisType]['end']);
×
UNCOV
1441
                        reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
×
UNCOV
1442
                        $sameEnd = (current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']));
×
1443

1444
                        if ($isShared === true && $sameEnd === true) {
×
1445
                            $badToken = $opener;
×
1446
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1447
                                $type = $this->tokens[$badToken]['type'];
×
1448
                                StatusWriter::write("* shared closer, cleaning up $badToken:$type *", ($level + 1));
×
1449
                            }
1450

1451
                            for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++) {
×
1452
                                $oldConditions = $this->tokens[$x]['conditions'];
×
UNCOV
1453
                                $oldLevel      = $this->tokens[$x]['level'];
×
UNCOV
1454
                                $this->tokens[$x]['level']--;
×
1455
                                unset($this->tokens[$x]['conditions'][$badToken]);
×
UNCOV
1456
                                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1457
                                    $type     = $this->tokens[$x]['type'];
×
1458
                                    $oldConds = '';
×
1459
                                    foreach ($oldConditions as $condition) {
×
UNCOV
1460
                                        $oldConds .= Tokens::tokenName($condition).',';
×
1461
                                    }
1462

UNCOV
1463
                                    $oldConds = rtrim($oldConds, ',');
×
1464

1465
                                    $newConds = '';
×
1466
                                    foreach ($this->tokens[$x]['conditions'] as $condition) {
×
1467
                                        $newConds .= Tokens::tokenName($condition).',';
×
1468
                                    }
1469

UNCOV
1470
                                    $newConds = rtrim($newConds, ',');
×
1471

1472
                                    $newLevel = $this->tokens[$x]['level'];
×
1473
                                    StatusWriter::write("* cleaned $x:$type *", ($level + 1));
×
1474
                                    StatusWriter::write("=> level changed from $oldLevel to $newLevel", ($level + 2));
×
UNCOV
1475
                                    StatusWriter::write("=> conditions changed from $oldConds to $newConds", ($level + 2));
×
1476
                                }//end if
1477
                            }//end for
1478

1479
                            unset($conditions[$badToken]);
×
1480
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1481
                                $type = $this->tokens[$badToken]['type'];
×
UNCOV
1482
                                StatusWriter::write("* token $badToken:$type removed from conditions array *", ($level + 1));
×
1483
                            }
1484

UNCOV
1485
                            unset($openers[$lastOpener]);
×
1486

1487
                            $level--;
×
1488
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1489
                                StatusWriter::write('* level decreased *', ($level + 2));
×
1490
                            }
1491
                        }//end if
1492
                    }//end if
1493

1494
                    $level++;
×
UNCOV
1495
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1496
                        StatusWriter::write('* level increased *', ($level + 1));
×
1497
                    }
1498

1499
                    $conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
×
UNCOV
1500
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1501
                        $type = $this->tokens[$stackPtr]['type'];
×
1502
                        StatusWriter::write("* token $stackPtr:$type added to conditions array *", ($level + 1));
×
1503
                    }
1504

1505
                    $lastOpener = $this->tokens[$i]['scope_opener'];
×
1506
                    if ($lastOpener !== null) {
×
1507
                        $openers[$lastOpener] = $lastOpener;
×
1508
                    }
1509
                } else if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
×
UNCOV
1510
                    foreach (array_reverse($openers) as $opener) {
×
UNCOV
1511
                        if ($this->tokens[$opener]['scope_closer'] === $i) {
×
1512
                            $oldOpener = array_pop($openers);
×
1513
                            if (empty($openers) === false) {
×
1514
                                $lastOpener           = array_pop($openers);
×
UNCOV
1515
                                $openers[$lastOpener] = $lastOpener;
×
1516
                            } else {
1517
                                $lastOpener = null;
×
1518
                            }
1519

UNCOV
1520
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1521
                                $type = $this->tokens[$oldOpener]['type'];
×
UNCOV
1522
                                StatusWriter::write("=> Found scope closer for $oldOpener:$type", ($level + 1));
×
1523
                            }
1524

1525
                            $oldCondition = array_pop($conditions);
×
1526
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1527
                                StatusWriter::write('* token '.Tokens::tokenName($oldCondition).' removed from conditions array *', ($level + 1));
×
1528
                            }
1529

1530
                            // Make sure this closer actually belongs to us.
1531
                            // Either the condition also has to think this is the
1532
                            // closer, or it has to allow sharing with us.
UNCOV
1533
                            $condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
×
UNCOV
1534
                            if ($condition !== $oldCondition) {
×
1535
                                if (isset($this->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
×
1536
                                    $badToken = $this->tokens[$oldOpener]['scope_condition'];
×
1537

1538
                                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1539
                                        $type = Tokens::tokenName($oldCondition);
×
1540
                                        StatusWriter::write("* scope closer was bad, cleaning up $badToken:$type *", ($level + 1));
×
1541
                                    }
1542

1543
                                    for ($x = ($oldOpener + 1); $x <= $i; $x++) {
×
1544
                                        $oldConditions = $this->tokens[$x]['conditions'];
×
UNCOV
1545
                                        $oldLevel      = $this->tokens[$x]['level'];
×
UNCOV
1546
                                        $this->tokens[$x]['level']--;
×
1547
                                        unset($this->tokens[$x]['conditions'][$badToken]);
×
UNCOV
1548
                                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
1549
                                            $type     = $this->tokens[$x]['type'];
×
1550
                                            $oldConds = '';
×
1551
                                            foreach ($oldConditions as $condition) {
×
UNCOV
1552
                                                $oldConds .= Tokens::tokenName($condition).',';
×
1553
                                            }
1554

UNCOV
1555
                                            $oldConds = rtrim($oldConds, ',');
×
1556

1557
                                            $newConds = '';
×
1558
                                            foreach ($this->tokens[$x]['conditions'] as $condition) {
×
1559
                                                $newConds .= Tokens::tokenName($condition).',';
×
1560
                                            }
1561

UNCOV
1562
                                            $newConds = rtrim($newConds, ',');
×
1563

UNCOV
1564
                                            $newLevel = $this->tokens[$x]['level'];
×
1565
                                            StatusWriter::write("* cleaned $x:$type *", ($level + 1));
×
1566
                                            StatusWriter::write("=> level changed from $oldLevel to $newLevel", ($level + 2));
×
1567
                                            StatusWriter::write("=> conditions changed from $oldConds to $newConds", ($level + 2));
×
1568
                                        }//end if
1569
                                    }//end for
1570
                                }//end if
1571
                            }//end if
1572

UNCOV
1573
                            $level--;
×
UNCOV
1574
                            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1575
                                StatusWriter::write('* level decreased *', ($level + 2));
×
1576
                            }
1577

1578
                            $this->tokens[$i]['level']      = $level;
×
1579
                            $this->tokens[$i]['conditions'] = $conditions;
×
1580
                        }//end if
1581
                    }//end foreach
1582
                }//end if
1583
            }//end if
1584
        }//end for
1585

UNCOV
1586
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
UNCOV
1587
            StatusWriter::write('*** END LEVEL MAP ***', 1);
×
1588
        }
1589

1590
    }//end createLevelMap()
1591

1592

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