Completed
Pull Request — master (#11)
by Tomáš
04:12
created

Tokenizer::getTokens()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 9.4285
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * The base tokenizer class.
4
 *
5
 * @author    Greg Sherwood <[email protected]>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/squizlabs/Symplify\PHP7_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9
10
namespace Symplify\PHP7_CodeSniffer\Tokenizers;
11
12
use Symplify\PHP7_CodeSniffer\RuntimeException;
13
use Symplify\PHP7_CodeSniffer\Util;
14
15
abstract class Tokenizer
16
{
17
18
    /**
19
     * The config data for the run.
20
     *
21
     * @var \Symplify\PHP7_CodeSniffer\Config
22
     */
23
    protected $config = null;
24
25
    /**
26
     * The EOL char used in the content.
27
     *
28
     * @var string
29
     */
30
    protected $eolChar = array();
31
32
    /**
33
     * A token-based representation of the content.
34
     *
35
     * @var array
36
     */
37
    protected $tokens = array();
38
39
    /**
40
     * Known lengths of tokens.
41
     *
42
     * @var array<int, int>
43
     */
44
    public $knownLengths = array();
45
46
    /**
47
     * A list of lines being ignored due to error suppression comments.
48
     *
49
     * @var array
50
     */
51
    public $ignoredLines = array();
52
53
54
    /**
55
     * Initialise and run the tokenizer.
56
     *
57
     * @param string                         $content The content to tokenize,
58
     * @param \Symplify\PHP7_CodeSniffer\Config | null $config  The config data for the run.
59
     * @param string                         $eolChar The EOL char used in the content.
60
     *
61
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
62
     * @throws TokenizerException If the file appears to be minified.
63
     */
64
    public function __construct($content, $config, $eolChar='\n')
65
    {
66
        $this->eolChar = $eolChar;
67
68
        $this->config = $config;
69
        $this->tokens = $this->tokenize($content);
70
71
        if ($config === null) {
72
            return;
73
        }
74
75
        $this->createPositionMap();
76
        $this->createTokenMap();
77
        $this->createParenthesisNestingMap();
78
        $this->createScopeMap();
79
        $this->createLevelMap();
80
81
        // Allow the tokenizer to do additional processing if required.
82
        $this->processAdditional();
83
84
    }//end __construct()
85
86
87
    /**
88
     * Checks the content to see if it looks minified.
89
     *
90
     * @param string $content The content to tokenize,
91
     * @param string $eolChar The EOL char used in the content.
92
     *
93
     * @return boolean
94
     */
95
    protected function isMinifiedContent($content, $eolChar='\n')
96
    {
97
        // Minified files often have a very large number of characters per line
98
        // and cause issues when tokenizing.
99
        $numChars = strlen($content);
100
        $numLines = (substr_count($content, $eolChar) + 1);
101
        $average  = ($numChars / $numLines);
102
        if ($average > 100) {
103
            return true;
104
        }
105
106
        return false;
107
108
    }//end isMinifiedContent()
109
110
111
    /**
112
     * Gets the array of tokens.
113
     *
114
     * @return array
115
     */
116
    public function getTokens()
117
    {
118
        return $this->tokens;
119
120
    }//end getTokens()
121
122
123
    /**
124
     * Creates an array of tokens when given some content.
125
     *
126
     * @param string $string The string to tokenize.
127
     *
128
     * @return array
129
     */
130
    abstract protected function tokenize($string);
131
132
133
    /**
134
     * Performs additional processing after main tokenizing.
135
     *
136
     * @return void
137
     */
138
    abstract protected function processAdditional();
139
140
141
    /**
142
     * Sets token position information.
143
     *
144
     * Can also convert tabs into spaces. Each tab can represent between
145
     * 1 and $width spaces, so this cannot be a straight string replace.
146
     *
147
     * @return void
148
     */
149
    private function createPositionMap()
150
    {
151
        $currColumn = 1;
152
        $lineNumber = 1;
153
        $eolLen     = (strlen($this->eolChar) * -1);
154
        $ignoring   = false;
155
        $inTests    = defined('Symplify\PHP7_CodeSniffer_IN_TESTS');
156
157
        $this->tokensWithTabs = array(
0 ignored issues
show
Bug introduced by
The property tokensWithTabs does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
158
                                 T_WHITESPACE               => true,
159
                                 T_COMMENT                  => true,
160
                                 T_DOC_COMMENT              => true,
161
                                 T_DOC_COMMENT_WHITESPACE   => true,
162
                                 T_DOC_COMMENT_STRING       => true,
163
                                 T_CONSTANT_ENCAPSED_STRING => true,
164
                                 T_DOUBLE_QUOTED_STRING     => true,
165
                                 T_HEREDOC                  => true,
166
                                 T_NOWDOC                   => true,
167
                                 T_INLINE_HTML              => true,
168
                                );
169
170
        $this->numTokens = count($this->tokens);
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
171
        for ($i = 0; $i < $this->numTokens; $i++) {
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
172
            $this->tokens[$i]['line']   = $lineNumber;
173
            $this->tokens[$i]['column'] = $currColumn;
174
175
            if (isset($this->knownLengths[$this->tokens[$i]['code']]) === true) {
176
                // There are no tabs in the tokens we know the length of.
177
                $length      = $this->knownLengths[$this->tokens[$i]['code']];
178
                $currColumn += $length;
179
            } else if ($this->config->tabWidth === 0
0 ignored issues
show
Documentation introduced by
The property tabWidth does not exist on object<Symplify\PHP7_CodeSniffer\Config>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
180
                || isset($this->tokensWithTabs[$this->tokens[$i]['code']]) === false
0 ignored issues
show
Bug introduced by
The property tokensWithTabs does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
181
                || strpos($this->tokens[$i]['content'], "\t") === false
182
            ) {
183
                $length = strlen($this->tokens[$i]['content']);
184
185
                $currColumn += $length;
186
            } else {
187
                $this->replaceTabsInToken($this->tokens[$i]);
188
                $length      = $this->tokens[$i]['length'];
189
                $currColumn += $length;
190
            }//end if
191
192
            $this->tokens[$i]['length'] = $length;
193
194
            if (isset($this->knownLengths[$this->tokens[$i]['code']]) === false
195
                && strpos($this->tokens[$i]['content'], $this->eolChar) !== false
196
            ) {
197
                $lineNumber++;
198
                $currColumn = 1;
199
200
                // Newline chars are not counted in the token length.
201
                $this->tokens[$i]['length'] += $eolLen;
202
            }
203
204
            if ($this->tokens[$i]['code'] === T_COMMENT
205
                || $this->tokens[$i]['code'] === T_DOC_COMMENT_TAG
206
                || ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML)
207
            ) {
208
                if (strpos($this->tokens[$i]['content'], '@codingStandards') !== false) {
209
                    if ($ignoring === false
210
                        && strpos($this->tokens[$i]['content'], '@codingStandardsIgnoreStart') !== false
211
                    ) {
212
                        $ignoring = true;
213
                    } else if ($ignoring === true
214
                        && strpos($this->tokens[$i]['content'], '@codingStandardsIgnoreEnd') !== false
215
                    ) {
216
                        $ignoring = false;
217
                        // Ignore this comment too.
218
                        $this->ignoredLines[$this->tokens[$i]['line']] = true;
219
                    } else if ($ignoring === false
220
                        && strpos($this->tokens[$i]['content'], '@codingStandardsIgnoreLine') !== false
221
                    ) {
222
                        $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = true;
223
                        // Ignore this comment too.
224
                        $this->ignoredLines[$this->tokens[$i]['line']] = true;
225
                    }
226
                }
227
            }//end if
228
229
            if ($ignoring === true) {
230
                $this->ignoredLines[$this->tokens[$i]['line']] = true;
231
            }
232
        }//end for
233
234
    }//end createPositionMap()
235
236
237
    /**
238
     * Replaces tabs in original token content with spaces.
239
     *
240
     * Each tab can represent between 1 and $config->tabWidth spaces,
241
     * so this cannot be a straight string replace. The original content
242
     * is placed into an orig_content index and the new token length is also
243
     * set in the length index.
244
     *
245
     * @param array  $token   The token to replace tabs inside.
246
     * @param string $prefix  The character to use to represent the start of a tab.
247
     * @param string $padding The character to use to represent the end of a tab.
248
     *
249
     * @return void
250
     */
251
    public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ')
252
    {
253
        $currColumn = $token['column'];
254
        $tabWidth   = $this->config->tabWidth;
0 ignored issues
show
Documentation introduced by
The property tabWidth does not exist on object<Symplify\PHP7_CodeSniffer\Config>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
255
        if ($tabWidth === 0) {
256
            $tabWidth = 1;
257
        }
258
259
        if (str_replace("\t", '', $token['content']) === '') {
260
            // String only contains tabs, so we can shortcut the process.
261
            $numTabs = strlen($token['content']);
262
263
            $newContent   = '';
0 ignored issues
show
Unused Code introduced by
$newContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
264
            $firstTabSize = ($tabWidth - ($currColumn % $tabWidth) + 1);
265
            $length       = ($firstTabSize + ($tabWidth * ($numTabs - 1)));
266
            $currColumn  += $length;
267
            $newContent   = $prefix.str_repeat($padding, ($length - 1));
268
        } else {
269
            // We need to determine the length of each tab.
270
            $tabs = explode("\t", $token['content']);
271
272
            $numTabs    = (count($tabs) - 1);
273
            $tabNum     = 0;
274
            $newContent = '';
275
            $length     = 0;
276
277
            foreach ($tabs as $content) {
278
                if ($content !== '') {
279
                    $newContent .= $content;
280
                    $contentLength = strlen($content);
281
282
                    $currColumn += $contentLength;
283
                    $length     += $contentLength;
284
                }
285
286
                // The last piece of content does not have a tab after it.
287
                if ($tabNum === $numTabs) {
288
                    break;
289
                }
290
291
                // Process the tab that comes after the content.
292
                $lastCurrColumn = $currColumn;
293
                $tabNum++;
294
295
                // Move the pointer to the next tab stop.
296
                if (($currColumn % $tabWidth) === 0) {
297
                    // This is the first tab, and we are already at a
298
                    // tab stop, so this tab counts as a single space.
299
                    $currColumn++;
300
                } else {
301
                    $currColumn++;
302
                    while (($currColumn % $tabWidth) !== 0) {
303
                        $currColumn++;
304
                    }
305
306
                    $currColumn++;
307
                }
308
309
                $length     += ($currColumn - $lastCurrColumn);
310
                $newContent .= $prefix.str_repeat($padding, ($currColumn - $lastCurrColumn - 1));
311
            }//end foreach
312
        }//end if
313
314
        $token['orig_content'] = $token['content'];
315
        $token['content']      = $newContent;
316
        $token['length']       = $length;
317
318
    }//end replaceTabsInToken()
319
320
321
    /**
322
     * Creates a map of brackets positions.
323
     *
324
     * @return void
325
     */
326
    private function createTokenMap()
327
    {
328
        if (PHP_CodeSniffer_VERBOSITY > 1) {
329
            echo "\t*** START TOKEN MAP ***".PHP_EOL;
330
        }
331
332
        $squareOpeners   = array();
333
        $curlyOpeners    = array();
334
        $this->numTokens = count($this->tokens);
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
335
336
        $openers   = array();
337
        $openOwner = null;
338
339
        for ($i = 0; $i < $this->numTokens; $i++) {
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
340
            /*
341
                Parenthesis mapping.
342
            */
343
344
            if (isset(Util\Tokens::$parenthesisOpeners[$this->tokens[$i]['code']]) === true) {
345
                $this->tokens[$i]['parenthesis_opener'] = null;
346
                $this->tokens[$i]['parenthesis_closer'] = null;
347
                $this->tokens[$i]['parenthesis_owner']  = $i;
348
                $openOwner = $i;
349
            } else if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
350
                $openers[] = $i;
351
                $this->tokens[$i]['parenthesis_opener'] = $i;
352
                if ($openOwner !== null) {
353
                    $this->tokens[$openOwner]['parenthesis_opener'] = $i;
354
                    $this->tokens[$i]['parenthesis_owner']          = $openOwner;
355
                    $openOwner = null;
356
                }
357
            } else if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
358
                // Did we set an owner for this set of parenthesis?
359
                $numOpeners = count($openers);
360
                if ($numOpeners !== 0) {
361
                    $opener = array_pop($openers);
362
                    if (isset($this->tokens[$opener]['parenthesis_owner']) === true) {
363
                        $owner = $this->tokens[$opener]['parenthesis_owner'];
364
365
                        $this->tokens[$owner]['parenthesis_closer'] = $i;
366
                        $this->tokens[$i]['parenthesis_owner']      = $owner;
367
                    }
368
369
                    $this->tokens[$i]['parenthesis_opener']      = $opener;
370
                    $this->tokens[$i]['parenthesis_closer']      = $i;
371
                    $this->tokens[$opener]['parenthesis_closer'] = $i;
372
                }
373
            }//end if
374
375
            /*
376
                Bracket mapping.
377
            */
378
379
            switch ($this->tokens[$i]['code']) {
380
            case T_OPEN_SQUARE_BRACKET:
381
                $squareOpeners[] = $i;
382
383 View Code Duplication
                if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
                    echo str_repeat("\t", count($squareOpeners));
385
                    echo str_repeat("\t", count($curlyOpeners));
386
                    echo "=> Found square bracket opener at $i".PHP_EOL;
387
                }
388
                break;
389
            case T_OPEN_CURLY_BRACKET:
390
                if (isset($this->tokens[$i]['scope_closer']) === false) {
391
                    $curlyOpeners[] = $i;
392
393 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394
                        echo str_repeat("\t", count($squareOpeners));
395
                        echo str_repeat("\t", count($curlyOpeners));
396
                        echo "=> Found curly bracket opener at $i".PHP_EOL;
397
                    }
398
                }
399
                break;
400
            case T_CLOSE_SQUARE_BRACKET:
401
                if (empty($squareOpeners) === false) {
402
                    $opener = array_pop($squareOpeners);
403
                    $this->tokens[$i]['bracket_opener']      = $opener;
404
                    $this->tokens[$i]['bracket_closer']      = $i;
405
                    $this->tokens[$opener]['bracket_opener'] = $opener;
406
                    $this->tokens[$opener]['bracket_closer'] = $i;
407
408 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
                        echo str_repeat("\t", count($squareOpeners));
410
                        echo str_repeat("\t", count($curlyOpeners));
411
                        echo "\t=> Found square bracket closer at $i for $opener".PHP_EOL;
412
                    }
413
                }
414
                break;
415
            case T_CLOSE_CURLY_BRACKET:
416
                if (empty($curlyOpeners) === false
417
                    && isset($this->tokens[$i]['scope_opener']) === false
418
                ) {
419
                    $opener = array_pop($curlyOpeners);
420
                    $this->tokens[$i]['bracket_opener']      = $opener;
421
                    $this->tokens[$i]['bracket_closer']      = $i;
422
                    $this->tokens[$opener]['bracket_opener'] = $opener;
423
                    $this->tokens[$opener]['bracket_closer'] = $i;
424
425 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
426
                        echo str_repeat("\t", count($squareOpeners));
427
                        echo str_repeat("\t", count($curlyOpeners));
428
                        echo "\t=> Found curly bracket closer at $i for $opener".PHP_EOL;
429
                    }
430
                }
431
                break;
432
            default:
433
                continue;
434
            }//end switch
435
        }//end for
436
437
        // Cleanup for any openers that we didn't find closers for.
438
        // This typically means there was a syntax error breaking things.
439
        foreach ($openers as $opener) {
440
            unset($this->tokens[$opener]['parenthesis_opener']);
441
            unset($this->tokens[$opener]['parenthesis_owner']);
442
        }
443
444
        if (PHP_CodeSniffer_VERBOSITY > 1) {
445
            echo "\t*** END TOKEN MAP ***".PHP_EOL;
446
        }
447
448
    }//end createTokenMap()
449
450
451
    /**
452
     * Creates a map for the parenthesis tokens that surround other tokens.
453
     *
454
     * @return void
455
     */
456
    private function createParenthesisNestingMap()
457
    {
458
        $map = array();
459
        for ($i = 0; $i < $this->numTokens; $i++) {
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
460
            if (isset($this->tokens[$i]['parenthesis_opener']) === true
461
                && $i === $this->tokens[$i]['parenthesis_opener']
462
            ) {
463
                if (empty($map) === false) {
464
                    $this->tokens[$i]['nested_parenthesis'] = $map;
465
                }
466
467
                if (isset($this->tokens[$i]['parenthesis_closer']) === true) {
468
                    $map[$this->tokens[$i]['parenthesis_opener']]
469
                        = $this->tokens[$i]['parenthesis_closer'];
470
                }
471
            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
472
                && $i === $this->tokens[$i]['parenthesis_closer']
473
            ) {
474
                array_pop($map);
475
                if (empty($map) === false) {
476
                    $this->tokens[$i]['nested_parenthesis'] = $map;
477
                }
478
            } else {
479
                if (empty($map) === false) {
480
                    $this->tokens[$i]['nested_parenthesis'] = $map;
481
                }
482
            }//end if
483
        }//end for
484
485
    }//end createParenthesisNestingMap()
486
487
488
    /**
489
     * Creates a scope map of tokens that open scopes.
490
     *
491
     * @return void
492
     * @see    recurseScopeMap()
493
     */
494
    private function createScopeMap()
495
    {
496
        if (PHP_CodeSniffer_VERBOSITY > 1) {
497
            echo "\t*** START SCOPE MAP ***".PHP_EOL;
498
        }
499
500
        for ($i = 0; $i < $this->numTokens; $i++) {
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
501
            // Check to see if the current token starts a new scope.
502
            if (isset($this->scopeOpeners[$this->tokens[$i]['code']]) === true) {
0 ignored issues
show
Bug introduced by
The property scopeOpeners does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
503
                if (PHP_CodeSniffer_VERBOSITY > 1) {
504
                    $type    = $this->tokens[$i]['type'];
505
                    $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
506
                    echo "\tStart scope map at $i:$type => $content".PHP_EOL;
507
                }
508
509
                if (isset($this->tokens[$i]['scope_condition']) === true) {
510
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
511
                        echo "\t* already processed, skipping *".PHP_EOL;
512
                    }
513
514
                    continue;
515
                }
516
517
                $i = $this->recurseScopeMap($i);
518
            }//end if
519
        }//end for
520
521
        if (PHP_CodeSniffer_VERBOSITY > 1) {
522
            echo "\t*** END SCOPE MAP ***".PHP_EOL;
523
        }
524
525
    }//end createScopeMap()
526
527
528
    /**
529
     * Recurses though the scope openers to build a scope map.
530
     *
531
     * @param int $stackPtr The position in the stack of the token that
532
     *                      opened the scope (eg. an IF token or FOR token).
533
     * @param int $depth    How many scope levels down we are.
534
     * @param int $ignore   How many curly braces we are ignoring.
535
     *
536
     * @return int The position in the stack that closed the scope.
537
     */
538
    private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
539
    {
540
        if (PHP_CodeSniffer_VERBOSITY > 1) {
541
            echo str_repeat("\t", $depth);
542
            echo "=> Begin scope map recursion at token $stackPtr with depth $depth".PHP_EOL;
543
        }
544
545
        $opener    = null;
546
        $currType  = $this->tokens[$stackPtr]['code'];
547
        $startLine = $this->tokens[$stackPtr]['line'];
548
549
        // We will need this to restore the value if we end up
550
        // returning a token ID that causes our calling function to go back
551
        // over already ignored braces.
552
        $originalIgnore = $ignore;
553
554
        // If the start token for this scope opener is the same as
555
        // the scope token, we have already found our opener.
556
        if (isset($this->scopeOpeners[$currType]['start'][$currType]) === true) {
557
            $opener = $stackPtr;
558
        }
559
560
        for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) {
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
561
            $tokenType = $this->tokens[$i]['code'];
562
563
            if (PHP_CodeSniffer_VERBOSITY > 1) {
564
                $type    = $this->tokens[$i]['type'];
565
                $line    = $this->tokens[$i]['line'];
566
                $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
567
568
                echo str_repeat("\t", $depth);
569
                echo "Process token $i on line $line [";
570
                if ($opener !== null) {
571
                    echo "opener:$opener;";
572
                }
573
574
                if ($ignore > 0) {
575
                    echo "ignore=$ignore;";
576
                }
577
578
                echo "]: $type => $content".PHP_EOL;
579
            }//end if
580
581
            // Very special case for IF statements in PHP that can be defined without
582
            // scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1;
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
583
            // If an IF statement below this one has an opener but no
584
            // keyword, the opener will be incorrectly assigned to this IF statement.
585
            if (($currType === T_IF || $currType === T_ELSE)
586
                && $opener === null
587
                && $this->tokens[$i]['code'] === T_SEMICOLON
588
            ) {
589 View Code Duplication
                if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                    $type = $this->tokens[$stackPtr]['type'];
591
                    echo str_repeat("\t", $depth);
592
                    echo "=> Found semicolon before scope opener for $stackPtr:$type, bailing".PHP_EOL;
593
                }
594
595
                return $i;
596
            }
597
598
            // Special case for PHP control structures that have no braces.
599
            // If we find a curly brace closer before we find the opener,
600
            // we're not going to find an opener. That closer probably belongs to
601
            // a control structure higher up.
602
            if ($opener === null
603
                && $ignore === 0
604
                && $tokenType === T_CLOSE_CURLY_BRACKET
605
                && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
606
            ) {
607 View Code Duplication
                if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
608
                    $type = $this->tokens[$stackPtr]['type'];
609
                    echo str_repeat("\t", $depth);
610
                    echo "=> Found curly brace closer before scope opener for $stackPtr:$type, bailing".PHP_EOL;
611
                }
612
613
                return ($i - 1);
614
            }
615
616
            if ($opener !== null
617
                && (isset($this->tokens[$i]['scope_opener']) === false
618
                || $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true)
619
                && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
620
            ) {
621
                if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
622
                    // The last opening bracket must have been for a string
623
                    // offset or alike, so let's ignore it.
624
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
625
                        echo str_repeat("\t", $depth);
626
                        echo '* finished ignoring curly brace *'.PHP_EOL;
627
                    }
628
629
                    $ignore--;
630
                    continue;
631
                } else if ($this->tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET
632
                    && $tokenType !== T_CLOSE_CURLY_BRACKET
633
                ) {
634
                    // The opener is a curly bracket so the closer must be a curly bracket as well.
635
                    // We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered
636
                    // a closer of T_IF when it should not.
637 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
638
                        $type = $this->tokens[$stackPtr]['type'];
639
                        echo str_repeat("\t", $depth);
640
                        echo "=> Ignoring non-curly scope closer for $stackPtr:$type".PHP_EOL;
641
                    }
642
                } else {
643
                    $scopeCloser = $i;
644
                    $todo        = array(
645
                                    $stackPtr,
646
                                    $opener,
647
                                   );
648
649 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
650
                        $type       = $this->tokens[$stackPtr]['type'];
651
                        $closerType = $this->tokens[$scopeCloser]['type'];
652
                        echo str_repeat("\t", $depth);
653
                        echo "=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type".PHP_EOL;
654
                    }
655
656
                    $validCloser = true;
657
                    if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF)
658
                        && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)
659
                    ) {
660
                        // To be a closer, this token must have an opener.
661
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
662
                            echo str_repeat("\t", $depth);
663
                            echo "* closer needs to be tested *".PHP_EOL;
664
                        }
665
666
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
667
668
                        if (isset($this->tokens[$scopeCloser]['scope_opener']) === false) {
669
                            $validCloser = false;
670
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
671
                                echo str_repeat("\t", $depth);
672
                                echo "* closer is not valid (no opener found) *".PHP_EOL;
673
                            }
674
                        } else if ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
675
                            $validCloser = false;
676
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
677
                                echo str_repeat("\t", $depth);
678
                                $type       = $this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['type'];
679
                                $openerType = $this->tokens[$opener]['type'];
680
                                echo "* closer is not valid (mismatched opener type; $type != $openerType) *".PHP_EOL;
681
                            }
682
                        } else if (PHP_CodeSniffer_VERBOSITY > 1) {
683
                            echo str_repeat("\t", $depth);
684
                            echo "* closer was valid *".PHP_EOL;
685
                        }
686
                    } else {
687
                        // The closer was not processed, so we need to
688
                        // complete that token as well.
689
                        $todo[] = $scopeCloser;
690
                    }//end if
691
692
                    if ($validCloser === true) {
693
                        foreach ($todo as $token) {
694
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
695
                            $this->tokens[$token]['scope_opener']    = $opener;
696
                            $this->tokens[$token]['scope_closer']    = $scopeCloser;
697
                        }
698
699
                        if ($this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true) {
700
                            // As we are going back to where we started originally, restore
701
                            // the ignore value back to its original value.
702
                            $ignore = $originalIgnore;
703
                            return $opener;
704
                        } else if ($scopeCloser === $i
705
                            && isset($this->scopeOpeners[$tokenType]) === true
706
                        ) {
707
                            // Unset scope_condition here or else the token will appear to have
708
                            // already been processed, and it will be skipped. Normally we want that,
709
                            // but in this case, the token is both a closer and an opener, so
710
                            // it needs to act like an opener. This is also why we return the
711
                            // token before this one; so the closer has a chance to be processed
712
                            // a second time, but as an opener.
713
                            unset($this->tokens[$scopeCloser]['scope_condition']);
714
                            return ($i - 1);
715
                        } else {
716
                            return $i;
717
                        }
718
                    } else {
719
                        continue;
720
                    }//end if
721
                }//end if
722
            }//end if
723
724
            // Is this an opening condition ?
725
            if (isset($this->scopeOpeners[$tokenType]) === true) {
726
                if ($opener === null) {
727
                    if ($tokenType === T_USE) {
728
                        // PHP use keywords are special because they can be
729
                        // used as blocks but also inline in function definitions.
730
                        // So if we find them nested inside another opener, just skip them.
731
                        continue;
732
                    }
733
734
                    if ($tokenType === T_FUNCTION
735
                        && $this->tokens[$stackPtr]['code'] !== T_FUNCTION
736
                    ) {
737
                        // Probably a closure, so process it manually.
738 View Code Duplication
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
739
                            $type = $this->tokens[$stackPtr]['type'];
740
                            echo str_repeat("\t", $depth);
741
                            echo "=> Found function before scope opener for $stackPtr:$type, processing manually".PHP_EOL;
742
                        }
743
744 View Code Duplication
                        if (isset($this->tokens[$i]['scope_closer']) === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
745
                            // We've already processed this closure.
746
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
747
                                echo str_repeat("\t", $depth);
748
                                echo '* already processed, skipping *'.PHP_EOL;
749
                            }
750
751
                            $i = $this->tokens[$i]['scope_closer'];
752
                            continue;
753
                        }
754
755
                        $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
756
                        continue;
757
                    }//end if
758
759
                    // Found another opening condition but still haven't
760
                    // found our opener, so we are never going to find one.
761 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
762
                        $type = $this->tokens[$stackPtr]['type'];
763
                        echo str_repeat("\t", $depth);
764
                        echo "=> Found new opening condition before scope opener for $stackPtr:$type, ";
765
                    }
766
767
                    if (($this->tokens[$stackPtr]['code'] === T_IF
768
                        || $this->tokens[$stackPtr]['code'] === T_ELSEIF
769
                        || $this->tokens[$stackPtr]['code'] === T_ELSE)
770
                        && ($this->tokens[$i]['code'] === T_ELSE
771
                        || $this->tokens[$i]['code'] === T_ELSEIF)
772
                    ) {
773
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
774
                            echo "continuing".PHP_EOL;
775
                        }
776
777
                        return ($i - 1);
778
                    } else {
779
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
780
                            echo "backtracking".PHP_EOL;
781
                        }
782
783
                        return $stackPtr;
784
                    }
785
                }//end if
786
787
                if (PHP_CodeSniffer_VERBOSITY > 1) {
788
                    echo str_repeat("\t", $depth);
789
                    echo '* token is an opening condition *'.PHP_EOL;
790
                }
791
792
                $isShared = ($this->scopeOpeners[$tokenType]['shared'] === true);
793
794
                if (isset($this->tokens[$i]['scope_condition']) === true) {
795
                    // We've been here before.
796
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
797
                        echo str_repeat("\t", $depth);
798
                        echo '* already processed, skipping *'.PHP_EOL;
799
                    }
800
801 View Code Duplication
                    if ($isShared === false
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
802
                        && isset($this->tokens[$i]['scope_closer']) === true
803
                    ) {
804
                        $i = $this->tokens[$i]['scope_closer'];
805
                    }
806
807
                    continue;
808
                } else if ($currType === $tokenType
809
                    && $isShared === false
810
                    && $opener === null
811
                ) {
812
                    // We haven't yet found our opener, but we have found another
813
                    // scope opener which is the same type as us, and we don't
814
                    // share openers, so we will never find one.
815
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
816
                        echo str_repeat("\t", $depth);
817
                        echo '* it was another token\'s opener, bailing *'.PHP_EOL;
818
                    }
819
820
                    return $stackPtr;
821
                } else {
822
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
823
                        echo str_repeat("\t", $depth);
824
                        echo '* searching for opener *'.PHP_EOL;
825
                    }
826
827 View Code Duplication
                    if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
828
                        $oldIgnore = $ignore;
829
                        $ignore    = 0;
830
                    }
831
832
                    // PHP has a max nesting level for functions. Stop before we hit that limit
833
                    // because too many loops means we've run into trouble anyway.
834 View Code Duplication
                    if ($depth > 50) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
835
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
836
                            echo str_repeat("\t", $depth);
837
                            echo '* reached maximum nesting level; aborting *'.PHP_EOL;
838
                        }
839
840
                        throw new RuntimeException('Maximum nesting level reached; file could not be processed');
841
                    }
842
843
                    $oldDepth = $depth;
844
                    if ($isShared === true
845
                        && isset($this->scopeOpeners[$tokenType]['with'][$currType]) === true
846
                    ) {
847
                        // Don't allow the depth to increment because this is
848
                        // possibly not a true nesting if we are sharing our closer.
849
                        // This can happen, for example, when a SWITCH has a large
850
                        // number of CASE statements with the same shared BREAK.
851
                        $depth--;
852
                    }
853
854
                    $i     = self::recurseScopeMap($i, ($depth + 1), $ignore);
855
                    $depth = $oldDepth;
856
857 View Code Duplication
                    if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
858
                        $ignore = $oldIgnore;
0 ignored issues
show
Bug introduced by
The variable $oldIgnore does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
859
                    }
860
                }//end if
861
            }//end if
862
863
            if (isset($this->scopeOpeners[$currType]['start'][$tokenType]) === true
864
                && $opener === null
865
            ) {
866
                if ($tokenType === T_OPEN_CURLY_BRACKET) {
867
                    if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true
868
                        && $i < $this->tokens[$stackPtr]['parenthesis_closer']
869
                    ) {
870
                        // We found a curly brace inside the condition of the
871
                        // current scope opener, so it must be a string offset.
872
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
873
                            echo str_repeat("\t", $depth);
874
                            echo '* ignoring curly brace *'.PHP_EOL;
875
                        }
876
877
                        $ignore++;
878
                    } else {
879
                        // Make sure this is actually an opener and not a
880
                        // string offset (e.g., $var{0}).
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
881
                        for ($x = ($i - 1); $x > 0; $x--) {
882
                            if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
883
                                continue;
884 View Code Duplication
                            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
885
                                // If the first non-whitespace/comment token is a
886
                                // variable or object operator then this is an opener
887
                                // for a string offset and not a scope.
888
                                if ($this->tokens[$x]['code'] === T_VARIABLE
889
                                    || $this->tokens[$x]['code'] === T_OBJECT_OPERATOR
890
                                ) {
891
                                    if (PHP_CodeSniffer_VERBOSITY > 1) {
892
                                        echo str_repeat("\t", $depth);
893
                                        echo '* ignoring curly brace *'.PHP_EOL;
894
                                    }
895
896
                                    $ignore++;
897
                                }//end if
898
899
                                break;
900
                            }//end if
901
                        }//end for
902
                    }//end if
903
                }//end if
904
905
                if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
906
                    // We found the opening scope token for $currType.
907 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
908
                        $type = $this->tokens[$stackPtr]['type'];
909
                        echo str_repeat("\t", $depth);
910
                        echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
911
                    }
912
913
                    $opener = $i;
914
                }
915
            } else if ($tokenType === T_OPEN_PARENTHESIS) {
916
                if (isset($this->tokens[$i]['parenthesis_owner']) === true) {
917
                    $owner = $this->tokens[$i]['parenthesis_owner'];
918 View Code Duplication
                    if (isset(Util\Tokens::$scopeOpeners[$this->tokens[$owner]['code']]) === true
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
919
                        && isset($this->tokens[$i]['parenthesis_closer']) === true
920
                    ) {
921
                        // If we get into here, then we opened a parenthesis for
922
                        // a scope (eg. an if or else if) so we need to update the
923
                        // start of the line so that when we check to see
924
                        // if the closing parenthesis is more than 3 lines away from
925
                        // the statement, we check from the closing parenthesis.
926
                        $startLine = $this->tokens[$this->tokens[$i]['parenthesis_closer']]['line'];
927
                    }
928
                }
929
            } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
930
                // We opened something that we don't have a scope opener for.
931
                // Examples of this are curly brackets for string offsets etc.
932
                // We want to ignore this so that we don't have an invalid scope
933
                // map.
934
                if (PHP_CodeSniffer_VERBOSITY > 1) {
935
                    echo str_repeat("\t", $depth);
936
                    echo '* ignoring curly brace *'.PHP_EOL;
937
                }
938
939
                $ignore++;
940
            } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
941
                // We found the end token for the opener we were ignoring.
942
                if (PHP_CodeSniffer_VERBOSITY > 1) {
943
                    echo str_repeat("\t", $depth);
944
                    echo '* finished ignoring curly brace *'.PHP_EOL;
945
                }
946
947
                $ignore--;
948
            } else if ($opener === null
949
                && isset($this->scopeOpeners[$currType]) === true
950
            ) {
951
                // If we still haven't found the opener after 3 lines,
952
                // we're not going to find it, unless we know it requires
953
                // an opener (in which case we better keep looking) or the last
954
                // token was empty (in which case we'll just confirm there is
955
                // more code in this file and not just a big comment).
956
                if ($this->tokens[$i]['line'] >= ($startLine + 3)
957
                    && isset(Util\Tokens::$emptyTokens[$this->tokens[($i - 1)]['code']]) === false
958
                ) {
959
                    if ($this->scopeOpeners[$currType]['strict'] === true) {
960 View Code Duplication
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
961
                            $type  = $this->tokens[$stackPtr]['type'];
962
                            $lines = ($this->tokens[$i]['line'] - $startLine);
963
                            echo str_repeat("\t", $depth);
964
                            echo "=> Still looking for $stackPtr:$type scope opener after $lines lines".PHP_EOL;
965
                        }
966 View Code Duplication
                    } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
967
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
968
                            $type = $this->tokens[$stackPtr]['type'];
969
                            echo str_repeat("\t", $depth);
970
                            echo "=> Couldn't find scope opener for $stackPtr:$type, bailing".PHP_EOL;
971
                        }
972
973
                        return $stackPtr;
974
                    }
975
                }
976
            } else if ($opener !== null
977
                && $tokenType !== T_BREAK
978
                && isset($this->endScopeTokens[$tokenType]) === true
0 ignored issues
show
Bug introduced by
The property endScopeTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
979
            ) {
980
                if (isset($this->tokens[$i]['scope_condition']) === false) {
981
                    if ($ignore > 0) {
982
                        // We found the end token for the opener we were ignoring.
983
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
984
                            echo str_repeat("\t", $depth);
985
                            echo '* finished ignoring curly brace *'.PHP_EOL;
986
                        }
987
988
                        $ignore--;
989
                    } else {
990
                        // We found a token that closes the scope but it doesn't
991
                        // have a condition, so it belongs to another token and
992
                        // our token doesn't have a closer, so pretend this is
993
                        // the closer.
994 View Code Duplication
                        if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
995
                            $type = $this->tokens[$stackPtr]['type'];
996
                            echo str_repeat("\t", $depth);
997
                            echo "=> Found (unexpected) scope closer for $stackPtr:$type".PHP_EOL;
998
                        }
999
1000 View Code Duplication
                        foreach (array($stackPtr, $opener) as $token) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1001
                            $this->tokens[$token]['scope_condition'] = $stackPtr;
1002
                            $this->tokens[$token]['scope_opener']    = $opener;
1003
                            $this->tokens[$token]['scope_closer']    = $i;
1004
                        }
1005
1006
                        return ($i - 1);
1007
                    }//end if
1008
                }//end if
1009
            }//end if
1010
        }//end for
1011
1012
        return $stackPtr;
1013
1014
    }//end recurseScopeMap()
1015
1016
1017
    /**
1018
     * Constructs the level map.
1019
     *
1020
     * The level map adds a 'level' index to each token which indicates the
1021
     * depth that a token within a set of scope blocks. It also adds a
1022
     * 'condition' index which is an array of the scope conditions that opened
1023
     * each of the scopes - position 0 being the first scope opener.
1024
     *
1025
     * @return void
1026
     */
1027
    private function createLevelMap()
1028
    {
1029
        if (PHP_CodeSniffer_VERBOSITY > 1) {
1030
            echo "\t*** START LEVEL MAP ***".PHP_EOL;
1031
        }
1032
1033
        $this->numTokens = count($this->tokens);
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1034
        $level           = 0;
1035
        $conditions      = array();
1036
        $lastOpener      = null;
1037
        $openers         = array();
1038
1039
        for ($i = 0; $i < $this->numTokens; $i++) {
0 ignored issues
show
Bug introduced by
The property numTokens does not seem to exist. Did you mean tokens?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1040
            if (PHP_CodeSniffer_VERBOSITY > 1) {
1041
                $type = $this->tokens[$i]['type'];
1042
                $line = $this->tokens[$i]['line'];
1043
                $len  = $this->tokens[$i]['length'];
1044
                $col  = $this->tokens[$i]['column'];
1045
1046
                $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
1047
1048
                echo str_repeat("\t", ($level + 1));
1049
                echo "Process token $i on line $line [col:$col;len:$len;lvl:$level;";
1050
                if (empty($conditions) !== true) {
1051
                    $condString = 'conds;';
1052
                    foreach ($conditions as $condition) {
1053
                        $condString .= token_name($condition).',';
1054
                    }
1055
1056
                    echo rtrim($condString, ',').';';
1057
                }
1058
1059
                echo "]: $type => $content".PHP_EOL;
1060
            }//end if
1061
1062
            $this->tokens[$i]['level']      = $level;
1063
            $this->tokens[$i]['conditions'] = $conditions;
1064
1065
            if (isset($this->tokens[$i]['scope_condition']) === true) {
1066
                // Check to see if this token opened the scope.
1067
                if ($this->tokens[$i]['scope_opener'] === $i) {
1068
                    $stackPtr = $this->tokens[$i]['scope_condition'];
1069 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1070
                        $type = $this->tokens[$stackPtr]['type'];
1071
                        echo str_repeat("\t", ($level + 1));
1072
                        echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
1073
                    }
1074
1075
                    $stackPtr = $this->tokens[$i]['scope_condition'];
1076
1077
                    // If we find a scope opener that has a shared closer,
1078
                    // then we need to go back over the condition map that we
1079
                    // just created and fix ourselves as we just added some
1080
                    // conditions where there was none. This happens for T_CASE
1081
                    // statements that are using the same break statement.
1082
                    if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
1083
                        // This opener shares its closer with the previous opener,
1084
                        // but we still need to check if the two openers share their
1085
                        // closer with each other directly (like CASE and DEFAULT)
1086
                        // or if they are just sharing because one doesn't have a
1087
                        // closer (like CASE with no BREAK using a SWITCHes closer).
1088
                        $thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
1089
                        $opener   = $this->tokens[$lastOpener]['scope_condition'];
1090
1091
                        $isShared = isset($this->scopeOpeners[$thisType]['with'][$this->tokens[$opener]['code']]);
1092
1093
                        reset($this->scopeOpeners[$thisType]['end']);
1094
                        reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
1095
                        $sameEnd = (current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']));
1096
1097
                        if ($isShared === true && $sameEnd === true) {
1098
                            $badToken = $opener;
1099
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
1100
                                $type = $this->tokens[$badToken]['type'];
1101
                                echo str_repeat("\t", ($level + 1));
1102
                                echo "* shared closer, cleaning up $badToken:$type *".PHP_EOL;
1103
                            }
1104
1105 View Code Duplication
                            for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1106
                                $oldConditions = $this->tokens[$x]['conditions'];
1107
                                $oldLevel      = $this->tokens[$x]['level'];
1108
                                $this->tokens[$x]['level']--;
1109
                                unset($this->tokens[$x]['conditions'][$badToken]);
1110
                                if (PHP_CodeSniffer_VERBOSITY > 1) {
1111
                                    $type     = $this->tokens[$x]['type'];
1112
                                    $oldConds = '';
1113
                                    foreach ($oldConditions as $condition) {
1114
                                        $oldConds .= token_name($condition).',';
1115
                                    }
1116
1117
                                    $oldConds = rtrim($oldConds, ',');
1118
1119
                                    $newConds = '';
1120
                                    foreach ($this->tokens[$x]['conditions'] as $condition) {
1121
                                        $newConds .= token_name($condition).',';
1122
                                    }
1123
1124
                                    $newConds = rtrim($newConds, ',');
1125
1126
                                    $newLevel = $this->tokens[$x]['level'];
1127
                                    echo str_repeat("\t", ($level + 1));
1128
                                    echo "* cleaned $x:$type *".PHP_EOL;
1129
                                    echo str_repeat("\t", ($level + 2));
1130
                                    echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
1131
                                    echo str_repeat("\t", ($level + 2));
1132
                                    echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
1133
                                }//end if
1134
                            }//end for
1135
1136
                            unset($conditions[$badToken]);
1137
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
1138
                                $type = $this->tokens[$badToken]['type'];
1139
                                echo str_repeat("\t", ($level + 1));
1140
                                echo "* token $badToken:$type removed from conditions array *".PHP_EOL;
1141
                            }
1142
1143
                            unset($openers[$lastOpener]);
1144
1145
                            $level--;
1146 View Code Duplication
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1147
                                echo str_repeat("\t", ($level + 2));
1148
                                echo '* level decreased *'.PHP_EOL;
1149
                            }
1150
                        }//end if
1151
                    }//end if
1152
1153
                    $level++;
1154 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1155
                        echo str_repeat("\t", ($level + 1));
1156
                        echo '* level increased *'.PHP_EOL;
1157
                    }
1158
1159
                    $conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
1160 View Code Duplication
                    if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1161
                        $type = $this->tokens[$stackPtr]['type'];
1162
                        echo str_repeat("\t", ($level + 1));
1163
                        echo "* token $stackPtr:$type added to conditions array *".PHP_EOL;
1164
                    }
1165
1166
                    $lastOpener = $this->tokens[$i]['scope_opener'];
1167
                    if ($lastOpener !== null) {
1168
                        $openers[$lastOpener] = $lastOpener;
1169
                    }
1170
                } else if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
1171
                    foreach (array_reverse($openers) as $opener) {
1172
                        if ($this->tokens[$opener]['scope_closer'] === $i) {
1173
                            $oldOpener = array_pop($openers);
1174
                            if (empty($openers) === false) {
1175
                                $lastOpener           = array_pop($openers);
1176
                                $openers[$lastOpener] = $lastOpener;
1177
                            } else {
1178
                                $lastOpener = null;
1179
                            }
1180
1181
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
1182
                                $type = $this->tokens[$oldOpener]['type'];
1183
                                echo str_repeat("\t", ($level + 1));
1184
                                echo "=> Found scope closer for $oldOpener:$type".PHP_EOL;
1185
                            }
1186
1187
                            $oldCondition = array_pop($conditions);
1188 View Code Duplication
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1189
                                echo str_repeat("\t", ($level + 1));
1190
                                echo '* token '.token_name($oldCondition).' removed from conditions array *'.PHP_EOL;
1191
                            }
1192
1193
                            // Make sure this closer actually belongs to us.
1194
                            // Either the condition also has to think this is the
1195
                            // closer, or it has to allow sharing with us.
1196
                            $condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
1197
                            if ($condition !== $oldCondition) {
1198
                                if (isset($this->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
1199
                                    $badToken = $this->tokens[$oldOpener]['scope_condition'];
1200
1201
                                    if (PHP_CodeSniffer_VERBOSITY > 1) {
1202
                                        $type = token_name($oldCondition);
1203
                                        echo str_repeat("\t", ($level + 1));
1204
                                        echo "* scope closer was bad, cleaning up $badToken:$type *".PHP_EOL;
1205
                                    }
1206
1207 View Code Duplication
                                    for ($x = ($oldOpener + 1); $x <= $i; $x++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1208
                                        $oldConditions = $this->tokens[$x]['conditions'];
1209
                                        $oldLevel      = $this->tokens[$x]['level'];
1210
                                        $this->tokens[$x]['level']--;
1211
                                        unset($this->tokens[$x]['conditions'][$badToken]);
1212
                                        if (PHP_CodeSniffer_VERBOSITY > 1) {
1213
                                            $type     = $this->tokens[$x]['type'];
1214
                                            $oldConds = '';
1215
                                            foreach ($oldConditions as $condition) {
1216
                                                $oldConds .= token_name($condition).',';
1217
                                            }
1218
1219
                                            $oldConds = rtrim($oldConds, ',');
1220
1221
                                            $newConds = '';
1222
                                            foreach ($this->tokens[$x]['conditions'] as $condition) {
1223
                                                $newConds .= token_name($condition).',';
1224
                                            }
1225
1226
                                            $newConds = rtrim($newConds, ',');
1227
1228
                                            $newLevel = $this->tokens[$x]['level'];
1229
                                            echo str_repeat("\t", ($level + 1));
1230
                                            echo "* cleaned $x:$type *".PHP_EOL;
1231
                                            echo str_repeat("\t", ($level + 2));
1232
                                            echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
1233
                                            echo str_repeat("\t", ($level + 2));
1234
                                            echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
1235
                                        }//end if
1236
                                    }//end for
1237
                                }//end if
1238
                            }//end if
1239
1240
                            $level--;
1241 View Code Duplication
                            if (PHP_CodeSniffer_VERBOSITY > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1242
                                echo str_repeat("\t", ($level + 2));
1243
                                echo '* level decreased *'.PHP_EOL;
1244
                            }
1245
1246
                            $this->tokens[$i]['level']      = $level;
1247
                            $this->tokens[$i]['conditions'] = $conditions;
1248
                        }//end if
1249
                    }//end foreach
1250
                }//end if
1251
            }//end if
1252
        }//end for
1253
1254
        if (PHP_CodeSniffer_VERBOSITY > 1) {
1255
            echo "\t*** END LEVEL MAP ***".PHP_EOL;
1256
        }
1257
1258
    }//end createLevelMap()
1259
1260
1261
}//end class
1262