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

PHPCSStandards / PHP_CodeSniffer / 15253296250

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

Pull #1105

github

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

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

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

60.08
/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<int|string, int|string>
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']);
118✔
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']) {
261✔
288
                                // Changed lines.
289
                                break;
228✔
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);
60✔
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()
213✔
622
    {
623
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
213✔
624
            StatusWriter::write('*** START TOKEN MAP ***', 1);
×
625
        }
626

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

631
        $openers = [];
213✔
632

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

638
            if (isset(Tokens::PARENTHESIS_OPENERS[$this->tokens[$i]['code']]) === true) {
213✔
639
                // Find the next non-empty token.
640
                $find = Tokens::EMPTY_TOKENS;
213✔
641
                if ($this->tokens[$i]['code'] === T_FUNCTION) {
213✔
642
                    $find[T_STRING]      = T_STRING;
210✔
643
                    $find[T_BITWISE_AND] = T_BITWISE_AND;
210✔
644
                }
645

646
                for ($j = ($i + 1); isset($this->tokens[$j], $find[$this->tokens[$j]['code']]) === true; $j++);
213✔
647
                if ($j < $this->numTokens && $this->tokens[$j]['code'] === T_OPEN_PARENTHESIS) {
213✔
648
                    $openers[] = $j;
210✔
649
                    $this->tokens[$i]['parenthesis_opener'] = $j;
210✔
650
                    $this->tokens[$i]['parenthesis_closer'] = null;
210✔
651
                    $this->tokens[$i]['parenthesis_owner']  = $i;
210✔
652

653
                    $this->tokens[$j]['parenthesis_opener'] = $j;
210✔
654
                    $this->tokens[$j]['parenthesis_owner']  = $i;
210✔
655

656
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
657
                        StatusWriter::write("=> Found parenthesis owner at $i", (count($openers) + 1));
×
658
                        StatusWriter::write("=> Found parenthesis opener at $j for $i", count($openers));
×
659
                    }
660

661
                    $i = $j;
211✔
662
                }
663
            } else if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
213✔
664
                $openers[] = $i;
210✔
665
                $this->tokens[$i]['parenthesis_opener'] = $i;
210✔
666

667
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
668
                    StatusWriter::write("=> Found unowned parenthesis opener at $i", count($openers));
70✔
669
                }
670
            } else if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
213✔
671
                // Did we set an owner for this set of parenthesis?
672
                $numOpeners = count($openers);
210✔
673
                if ($numOpeners !== 0) {
210✔
674
                    $opener = array_pop($openers);
210✔
675
                    if (isset($this->tokens[$opener]['parenthesis_owner']) === true) {
210✔
676
                        $owner = $this->tokens[$opener]['parenthesis_owner'];
210✔
677

678
                        $this->tokens[$owner]['parenthesis_closer'] = $i;
210✔
679
                        $this->tokens[$i]['parenthesis_owner']      = $owner;
210✔
680

681
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
682
                            StatusWriter::write("=> Found parenthesis closer at $i for $owner", (count($openers) + 1));
70✔
683
                        }
684
                    } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
685
                        StatusWriter::write("=> Found unowned parenthesis closer at $i for $opener", (count($openers) + 1));
×
686
                    }
687

688
                    $this->tokens[$i]['parenthesis_opener']      = $opener;
210✔
689
                    $this->tokens[$i]['parenthesis_closer']      = $i;
210✔
690
                    $this->tokens[$opener]['parenthesis_closer'] = $i;
210✔
691
                }//end if
692
            } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE) {
213✔
693
                $openers[] = $i;
×
694
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
695
                    StatusWriter::write("=> Found attribute opener at $i", count($openers));
×
696
                }
697

698
                $this->tokens[$i]['attribute_opener'] = $i;
×
699
                $this->tokens[$i]['attribute_closer'] = null;
×
700
            } else if ($this->tokens[$i]['code'] === T_ATTRIBUTE_END) {
213✔
701
                $numOpeners = count($openers);
×
702
                if ($numOpeners !== 0) {
×
703
                    $opener = array_pop($openers);
×
704
                    if (isset($this->tokens[$opener]['attribute_opener']) === true) {
×
705
                        $this->tokens[$opener]['attribute_closer'] = $i;
×
706

707
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
708
                            StatusWriter::write("=> Found attribute closer at $i for $opener", (count($openers) + 1));
×
709
                        }
710

711
                        for ($x = ($opener + 1); $x <= $i; ++$x) {
×
712
                            if (isset($this->tokens[$x]['attribute_closer']) === true) {
×
713
                                continue;
×
714
                            }
715

716
                            $this->tokens[$x]['attribute_opener'] = $opener;
×
717
                            $this->tokens[$x]['attribute_closer'] = $i;
×
718
                        }
719
                    } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
720
                        StatusWriter::write("=> Found unowned attribute closer at $i for $opener", (count($openers) + 1));
×
721
                    }
722
                }//end if
723
            }//end if
724

725
            /*
726
                Bracket mapping.
727
            */
728

729
            switch ($this->tokens[$i]['code']) {
213✔
730
            case T_OPEN_SQUARE_BRACKET:
213✔
731
                $squareOpeners[] = $i;
57✔
732

733
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
734
                    StatusWriter::write("=> Found square bracket opener at $i", (count($squareOpeners) + count($curlyOpeners)));
×
735
                }
736
                break;
57✔
737
            case T_OPEN_CURLY_BRACKET:
213✔
738
                if (isset($this->tokens[$i]['scope_closer']) === false) {
210✔
739
                    $curlyOpeners[] = $i;
210✔
740

741
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
742
                        StatusWriter::write("=> Found curly bracket opener at $i", (count($squareOpeners) + count($curlyOpeners)));
×
743
                    }
744
                }
745
                break;
210✔
746
            case T_CLOSE_SQUARE_BRACKET:
213✔
747
                if (empty($squareOpeners) === false) {
57✔
748
                    $opener = array_pop($squareOpeners);
57✔
749
                    $this->tokens[$i]['bracket_opener']      = $opener;
57✔
750
                    $this->tokens[$i]['bracket_closer']      = $i;
57✔
751
                    $this->tokens[$opener]['bracket_opener'] = $opener;
57✔
752
                    $this->tokens[$opener]['bracket_closer'] = $i;
57✔
753

754
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
57✔
755
                        StatusWriter::write("=> Found square bracket closer at $i for $opener", (count($squareOpeners) + count($curlyOpeners) + 1));
×
756
                    }
757
                }
758
                break;
57✔
759
            case T_CLOSE_CURLY_BRACKET:
213✔
760
                if (empty($curlyOpeners) === false
210✔
761
                    && isset($this->tokens[$i]['scope_opener']) === false
210✔
762
                ) {
763
                    $opener = array_pop($curlyOpeners);
210✔
764
                    $this->tokens[$i]['bracket_opener']      = $opener;
210✔
765
                    $this->tokens[$i]['bracket_closer']      = $i;
210✔
766
                    $this->tokens[$opener]['bracket_opener'] = $opener;
210✔
767
                    $this->tokens[$opener]['bracket_closer'] = $i;
210✔
768

769
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
210✔
770
                        StatusWriter::write("=> Found curly bracket closer at $i for $opener", (count($squareOpeners) + count($curlyOpeners) + 1));
×
771
                    }
772
                }
773
                break;
210✔
774
            default:
775
                continue 2;
213✔
776
            }//end switch
777
        }//end for
778

779
        // Cleanup for any openers that we didn't find closers for.
780
        // This typically means there was a syntax error breaking things.
781
        foreach ($openers as $opener) {
213✔
782
            unset($this->tokens[$opener]['parenthesis_opener']);
×
783
            unset($this->tokens[$opener]['parenthesis_owner']);
×
784
        }
785

786
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
213✔
787
            StatusWriter::write('*** END TOKEN MAP ***', 1);
×
788
        }
789

790
    }//end createTokenMap()
71✔
791

792

793
    /**
794
     * Creates a map for the parenthesis tokens that surround other tokens.
795
     *
796
     * @return void
797
     */
798
    private function createParenthesisNestingMap()
204✔
799
    {
800
        $map = [];
204✔
801
        for ($i = 0; $i < $this->numTokens; $i++) {
204✔
802
            if (isset($this->tokens[$i]['parenthesis_opener']) === true
204✔
803
                && $i === $this->tokens[$i]['parenthesis_opener']
204✔
804
            ) {
805
                if (empty($map) === false) {
204✔
806
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
807
                }
808

809
                if (isset($this->tokens[$i]['parenthesis_closer']) === true) {
204✔
810
                    $map[$this->tokens[$i]['parenthesis_opener']]
204✔
811
                        = $this->tokens[$i]['parenthesis_closer'];
204✔
812
                }
813
            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
204✔
814
                && $i === $this->tokens[$i]['parenthesis_closer']
204✔
815
            ) {
816
                array_pop($map);
204✔
817
                if (empty($map) === false) {
204✔
818
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
819
                }
820
            } else {
821
                if (empty($map) === false) {
204✔
822
                    $this->tokens[$i]['nested_parenthesis'] = $map;
204✔
823
                }
824
            }//end if
825
        }//end for
826

827
    }//end createParenthesisNestingMap()
68✔
828

829

830
    /**
831
     * Creates a scope map of tokens that open scopes.
832
     *
833
     * @return void
834
     * @see    recurseScopeMap()
835
     */
836
    private function createScopeMap()
×
837
    {
838
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
839
            StatusWriter::write('*** START SCOPE MAP ***', 1);
×
840
        }
841

842
        for ($i = 0; $i < $this->numTokens; $i++) {
×
843
            // Check to see if the current token starts a new scope.
844
            if (isset($this->scopeOpeners[$this->tokens[$i]['code']]) === true) {
×
845
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
846
                    $type    = $this->tokens[$i]['type'];
×
847
                    $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
848
                    StatusWriter::write("Start scope map at $i:$type => $content", 1);
×
849
                }
850

851
                if (isset($this->tokens[$i]['scope_condition']) === true) {
×
852
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
853
                        StatusWriter::write('* already processed, skipping *', 1);
×
854
                    }
855

856
                    continue;
×
857
                }
858

859
                $i = $this->recurseScopeMap($i);
×
860
            }//end if
861
        }//end for
862

863
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
864
            StatusWriter::write('*** END SCOPE MAP ***', 1);
×
865
        }
866

867
    }//end createScopeMap()
868

869

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

887
        $opener    = null;
222✔
888
        $currType  = $this->tokens[$stackPtr]['code'];
222✔
889
        $startLine = $this->tokens[$stackPtr]['line'];
222✔
890

891
        // We will need this to restore the value if we end up
892
        // returning a token ID that causes our calling function to go back
893
        // over already ignored braces.
894
        $originalIgnore = $ignore;
222✔
895

896
        // If the start token for this scope opener is the same as
897
        // the scope token, we have already found our opener.
898
        if (isset($this->scopeOpeners[$currType]['start'][$currType]) === true) {
222✔
899
            $opener = $stackPtr;
×
900
        }
901

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

905
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
906
                $type    = $this->tokens[$i]['type'];
×
907
                $line    = $this->tokens[$i]['line'];
×
908
                $content = Common::prepareForOutput($this->tokens[$i]['content']);
×
909

910
                $statusMessage = "Process token $i on line $line [";
×
911
                if ($opener !== null) {
×
912
                    $statusMessage .= "opener:$opener;";
×
913
                }
914

915
                if ($ignore > 0) {
×
916
                    $statusMessage .= "ignore=$ignore;";
×
917
                }
918

919
                $statusMessage .= "]: $type => $content";
×
920
                StatusWriter::write($statusMessage, $depth);
×
921
            }//end if
922

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

942
                    StatusWriter::write("=> Found $closerType before scope opener for $stackPtr:$type, bailing", $depth);
×
943
                }
944

945
                return $i;
72✔
946
            }
947

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

962
                return ($i - 1);
×
963
            }
964

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

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

996
                    if (PHP_CODESNIFFER_VERBOSITY > 1) {
222✔
997
                        $type       = $this->tokens[$stackPtr]['type'];
×
998
                        $closerType = $this->tokens[$scopeCloser]['type'];
×
999
                        StatusWriter::write("=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type", $depth);
×
1000
                    }
1001

1002
                    $validCloser = true;
222✔
1003
                    if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF)
222✔
1004
                        && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)
222✔
1005
                    ) {
1006
                        // To be a closer, this token must have an opener.
1007
                        if (PHP_CODESNIFFER_VERBOSITY > 1) {
3✔
1008
                            StatusWriter::write('* closer needs to be tested *', $depth);
×
1009
                        }
1010

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

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

1034
                    if ($validCloser === true) {
222✔
1035
                        foreach ($todo as $token) {
222✔
1036
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
222✔
1037
                            $this->tokens[$token]['scope_opener']    = $opener;
222✔
1038
                            $this->tokens[$token]['scope_closer']    = $scopeCloser;
222✔
1039
                        }
1040

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

1066
            // Is this an opening condition ?
1067
            if (isset($this->scopeOpeners[$tokenType]) === true) {
222✔
1068
                if ($opener === null) {
204✔
1069
                    if ($tokenType === T_USE) {
18✔
1070
                        // PHP use keywords are special because they can be
1071
                        // used as blocks but also inline in function definitions.
1072
                        // So if we find them nested inside another opener, just skip them.
1073
                        continue;
×
1074
                    }
1075

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1258
                                    $ignore++;
×
1259
                                }
1260

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

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

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

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

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

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

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

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

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

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

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

1395
        return $stackPtr;
×
1396

1397
    }//end recurseScopeMap()
1398

1399

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1625
    }//end createLevelMap()
1626

1627

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

© 2025 Coveralls, Inc