Completed
Push — master ( da65dc...5751ed )
by Tomáš
20:43 queued 16:49
created

File::addMessage()   F

Complexity

Conditions 38
Paths > 20000

Size

Total Lines 179
Code Lines 98

Duplication

Lines 26
Ratio 14.53 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 26
loc 179
rs 2
cc 38
eloc 98
nc 173821
nop 8

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Represents a piece of content being checked during the run.
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\Files;
11
12
use Symplify\PHP7_CodeSniffer\Ruleset;
13
use Symplify\PHP7_CodeSniffer\Config;
14
use Symplify\PHP7_CodeSniffer\Fixer;
15
use Symplify\PHP7_CodeSniffer\Util;
16
use Symplify\PHP7_CodeSniffer\Exceptions\RuntimeException;
17
use Symplify\PHP7_CodeSniffer\Exceptions\TokenizerException;
18
19
class File
20
{
21
22
    /**
23
     * The absolute path to the file associated with this object.
24
     *
25
     * @var string
26
     */
27
    public $path = '';
28
29
    /**
30
     * The absolute path to the file associated with this object.
31
     *
32
     * @var string
33
     */
34
    protected $content = '';
35
36
    /**
37
     * The config data for the run.
38
     *
39
     * @var \Symplify\PHP7_CodeSniffer\Config
40
     */
41
    public $config = null;
42
43
    /**
44
     * The ruleset used for the run.
45
     *
46
     * @var \Symplify\PHP7_CodeSniffer\Ruleset
47
     */
48
    public $ruleset = null;
49
50
    /**
51
     * If TRUE, the entire file is being ignored.
52
     *
53
     * @var string
54
     */
55
    public $ignored = false;
56
57
    /**
58
     * The EOL character this file uses.
59
     *
60
     * @var string
61
     */
62
    public $eolChar = '';
63
64
    /**
65
     * The Fixer object to control fixing errors.
66
     *
67
     * @var \Symplify\PHP7_CodeSniffer\Fixer
68
     */
69
    public $fixer = null;
70
71
    /**
72
     * The tokenizer being used for this file.
73
     *
74
     * @var \Symplify\PHP7_CodeSniffer\Tokenizers\Tokenizer
75
     */
76
    public $tokenizer = null;
77
78
    /**
79
     * Was the file loaded from cache?
80
     *
81
     * If TRUE, the file was loaded from a local cache.
82
     * If FALSE, the file was tokenized and processed fully.
83
     *
84
     * @var boolean
85
     */
86
    public $fromCache = false;
87
88
    /**
89
     * The number of tokens in this file.
90
     *
91
     * Stored here to save calling count() everywhere.
92
     *
93
     * @var integer
94
     */
95
    public $numTokens = 0;
96
97
    /**
98
     * The tokens stack map.
99
     *
100
     * @var array
101
     */
102
    protected $tokens = array();
103
104
    /**
105
     * The errors raised from sniffs.
106
     *
107
     * @var array
108
     * @see getErrors()
109
     */
110
    protected $errors = array();
111
112
    /**
113
     * The warnings raised from sniffs.
114
     *
115
     * @var array
116
     * @see getWarnings()
117
     */
118
    protected $warnings = array();
119
120
    /**
121
     * The metrics recorded by sniffs.
122
     *
123
     * @var array
124
     * @see getMetrics()
125
     */
126
    protected $metrics = array();
127
128
    /**
129
     * The total number of errors raised.
130
     *
131
     * @var integer
132
     */
133
    protected $errorCount = 0;
134
135
    /**
136
     * The total number of warnings raised.
137
     *
138
     * @var integer
139
     */
140
    protected $warningCount = 0;
141
142
    /**
143
     * The total number of errors and warnings that can be fixed.
144
     *
145
     * @var integer
146
     */
147
    protected $fixableCount = 0;
148
149
    /**
150
     * An array of sniffs that are being ignored.
151
     *
152
     * @var array
153
     */
154
    protected $ignoredListeners = array();
155
156
    /**
157
     * An array of message codes that are being ignored.
158
     *
159
     * @var array
160
     */
161
    protected $ignoredCodes = array();
162
163
    /**
164
     * An array of sniffs listening to this file's processing.
165
     *
166
     * @var \Symplify\PHP7_CodeSniffer\Sniffs\Sniff[]
167
     */
168
    protected $listeners = array();
169
170
    /**
171
     * The class name of the sniff currently processing the file.
172
     *
173
     * @var string
174
     */
175
    protected $activeListener = '';
176
177
    /**
178
     * An array of sniffs being processed and how long they took.
179
     *
180
     * @var array
181
     */
182
    protected $listenerTimes = array();
183
184
    /**
185
     * A cache of often used config settings to improve performance.
186
     *
187
     * Storing them here saves 10k+ calls to __get() in the Config class.
188
     *
189
     * @var array
190
     */
191
    protected $configCache = array();
192
193
194
    /**
195
     * Constructs a file.
196
     *
197
     * @param string                   $path    The absolute path to the file to process.
198
     * @param \Symplify\PHP7_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
199
     * @param \Symplify\PHP7_CodeSniffer\Config  $config  The config data for the run.
200
     *
201
     * @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...
202
     */
203
    public function __construct($path, Ruleset $ruleset, Config $config)
204
    {
205
        $this->path    = $path;
206
        $this->ruleset = $ruleset;
207
        $this->config  = $config;
208
        $this->fixer   = new Fixer();
209
210
        $parts     = explode('.', $path);
211
        $extension = array_pop($parts);
212
        if (isset($config->extensions[$extension]) === true) {
0 ignored issues
show
Documentation introduced by
The property extensions 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...
213
            $this->tokenizerType = $config->extensions[$extension];
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
Documentation introduced by
The property extensions 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...
214
        } else {
215
            // Revert to default.
216
            $this->tokenizerType = 'PHP';
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
217
        }
218
219
        $this->configCache['cache']           = $this->config->cache;
0 ignored issues
show
Documentation introduced by
The property cache 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...
220
        $this->configCache['sniffs']          = $this->config->sniffs;
0 ignored issues
show
Documentation introduced by
The property sniffs 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...
221
        $this->configCache['errorSeverity']   = $this->config->errorSeverity;
0 ignored issues
show
Documentation introduced by
The property errorSeverity 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...
222
        $this->configCache['warningSeverity'] = $this->config->warningSeverity;
0 ignored issues
show
Documentation introduced by
The property warningSeverity 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...
223
        $this->configCache['recordErrors']    = $this->config->recordErrors;
0 ignored issues
show
Documentation introduced by
The property recordErrors 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...
224
        $this->configCache['ignorePatterns']  = $this->ruleset->getIgnorePatterns();
225
226
    }//end __construct()
227
228
229
    /**
230
     * Set the content of the file.
231
     *
232
     * Setting the content also calculates the EOL char being used.
233
     *
234
     * @param string $content The file content.
235
     *
236
     * @return void
237
     */
238
    function setContent($content)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
239
    {
240
        $this->content = $content;
241
        $this->tokens  = array();
242
243
        try {
244
            $this->eolChar = Util\Common::detectLineEndings($content);
245
        } catch (RuntimeException $e) {
246
            $this->addWarningOnLine($e->getMessage(), 1, 'Internal.DetectLineEndings');
247
            return;
248
        }
249
250
    }//end setContent()
251
252
253
    /**
254
     * Reloads the content of the file.
255
     *
256
     * By default, we have no idea where our content comes from,
257
     * so we can't do anything.
258
     *
259
     * @return void
260
     */
261
    function reloadContent()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
262
    {
263
264
    }//end reloadContent()
265
266
267
    /**
268
     * Disables caching of this file.
269
     *
270
     * @return void
271
     */
272
    function disableCaching()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
273
    {
274
        $this->configCache['cache'] = false;
275
276
    }//end disableCaching()
277
278
279
    /**
280
     * Starts the stack traversal and tells listeners when tokens are found.
281
     *
282
     * @return void
283
     */
284
    public function process()
285
    {
286
        if ($this->ignored === true) {
287
            return;
288
        }
289
290
        $this->errors       = array();
291
        $this->warnings     = array();
292
        $this->errorCount   = 0;
293
        $this->warningCount = 0;
294
        $this->fixableCount = 0;
295
296
        $this->parse();
297
298
        $this->fixer->startFile($this);
299
300
        if (PHP_CodeSniffer_VERBOSITY > 2) {
301
            echo "\t*** START TOKEN PROCESSING ***".PHP_EOL;
302
        }
303
304
        $foundCode        = false;
305
        $listenerIgnoreTo = array();
306
        $inTests          = defined('Symplify\PHP7_CodeSniffer_IN_TESTS');
307
308
        // Foreach of the listeners that have registered to listen for this
309
        // token, get them to process it.
310
        foreach ($this->tokens as $stackPtr => $token) {
311
            // Check for ignored lines.
312
            if ($token['code'] === T_COMMENT
313
                || $token['code'] === T_DOC_COMMENT_TAG
314
                || ($inTests === true && $token['code'] === T_INLINE_HTML)
315
            ) {
316
                if (strpos($token['content'], '@codingStandards') !== false) {
317
                    if (strpos($token['content'], '@codingStandardsIgnoreFile') !== false) {
318
                        // Ignoring the whole file, just a little late.
319
                        $this->errors       = array();
320
                        $this->warnings     = array();
321
                        $this->errorCount   = 0;
322
                        $this->warningCount = 0;
323
                        $this->fixableCount = 0;
324
                        return;
325
                    } else if (strpos($token['content'], '@codingStandardsChangeSetting') !== false) {
326
                        $start   = strpos($token['content'], '@codingStandardsChangeSetting');
327
                        $comment = substr($token['content'], ($start + 30));
328
                        $parts   = explode(' ', $comment);
329
                        if ($parts >= 3) {
330
                            $sniffParts = explode('.', $parts[0]);
331
                            if ($sniffParts >= 3) {
332
                                // If the sniff code is not know to us, it has not been registered in this run.
333
                                // But don't throw an error as it could be there for a different standard to use.
334
                                if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
335
                                    $listenerClass = $this->ruleset->sniffCodes[$parts[0]];
336
                                    $this->ruleset->setSniffProperty($listenerClass, $parts[1], $parts[2]);
337
                                }
338
                            }
339
                        }
340
                    }//end if
341
                }//end if
342
            }//end if
343
344
            if (PHP_CodeSniffer_VERBOSITY > 2) {
345
                $type    = $token['type'];
346
                $content = Util\Common::prepareForOutput($token['content']);
347
                echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL;
348
            }
349
350
            if ($token['code'] !== T_INLINE_HTML) {
351
                $foundCode = true;
352
            }
353
354
            if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
355
                continue;
356
            }
357
358
            foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
359
                if (isset($this->ignoredListeners[$listenerData['class']]) === true
360
                    || (isset($listenerIgnoreTo[$listenerData['class']]) === true
361
                    && $listenerIgnoreTo[$listenerData['class']] > $stackPtr)
362
                ) {
363
                    // This sniff is ignoring past this token, or the whole file.
364
                    continue;
365
                }
366
367
                // Make sure this sniff supports the tokenizer
368
                // we are currently using.
369
                $class = $listenerData['class'];
370
371
                if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) {
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
372
                    continue;
373
                }
374
375
                // If the file path matches one of our ignore patterns, skip it.
376
                // While there is support for a type of each pattern
377
                // (absolute or relative) we don't actually support it here.
378 View Code Duplication
                foreach ($listenerData['ignore'] as $pattern) {
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...
379
                    // We assume a / directory separator, as do the exclude rules
380
                    // most developers write, so we need a special case for any system
381
                    // that is different.
382
                    if (DIRECTORY_SEPARATOR === '\\') {
383
                        $pattern = str_replace('/', '\\\\', $pattern);
384
                    }
385
386
                    $pattern = '`'.$pattern.'`i';
387
                    if (preg_match($pattern, $this->path) === 1) {
388
                        $this->ignoredListeners[$class] = true;
389
                        continue(2);
390
                    }
391
                }
392
393
                // If the file path does not match one of our include patterns, skip it.
394
                // While there is support for a type of each pattern
395
                // (absolute or relative) we don't actually support it here.
396 View Code Duplication
                foreach ($listenerData['include'] as $pattern) {
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...
397
                    // We assume a / directory separator, as do the exclude rules
398
                    // most developers write, so we need a special case for any system
399
                    // that is different.
400
                    if (DIRECTORY_SEPARATOR === '\\') {
401
                        $pattern = str_replace('/', '\\\\', $pattern);
402
                    }
403
404
                    $pattern = '`'.$pattern.'`i';
405
                    if (preg_match($pattern, $this->path) !== 1) {
406
                        $this->ignoredListeners[$class] = true;
407
                        continue(2);
408
                    }
409
                }
410
411
                $this->activeListener = $class;
412
413
                if (PHP_CodeSniffer_VERBOSITY > 2) {
414
                    $startTime = microtime(true);
415
                    echo "\t\t\tProcessing ".$this->activeListener.'... ';
416
                }
417
418
                $ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
419
                if ($ignoreTo !== null) {
420
                    $listenerIgnoreTo[$this->activeListener] = $ignoreTo;
421
                }
422
423
                if (PHP_CodeSniffer_VERBOSITY > 2) {
424
                    $timeTaken = (microtime(true) - $startTime);
0 ignored issues
show
Bug introduced by
The variable $startTime 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...
425
                    if (isset($this->listenerTimes[$this->activeListener]) === false) {
426
                        $this->listenerTimes[$this->activeListener] = 0;
427
                    }
428
429
                    $this->listenerTimes[$this->activeListener] += $timeTaken;
430
431
                    $timeTaken = round(($timeTaken), 4);
432
                    echo "DONE in $timeTaken seconds".PHP_EOL;
433
                }
434
435
                $this->activeListener = '';
436
            }//end foreach
437
        }//end foreach
438
439
        // If short open tags are off but the file being checked uses
440
        // short open tags, the whole content will be inline HTML
441
        // and nothing will be checked. So try and handle this case.
442
        if ($foundCode === false && $this->tokenizerType === 'PHP') {
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
443
            $shortTags = (bool) ini_get('short_open_tag');
444
            if ($shortTags === false) {
445
                $error = 'No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags but PHP does not allow them.';
446
                $this->addWarning($error, null, 'Internal.NoCodeFound');
447
            }
448
        }
449
450
        if (PHP_CodeSniffer_VERBOSITY > 2) {
451
            echo "\t*** END TOKEN PROCESSING ***".PHP_EOL;
452
            echo "\t*** START SNIFF PROCESSING REPORT ***".PHP_EOL;
453
454
            asort($this->listenerTimes, SORT_NUMERIC);
455
            $this->listenerTimes = array_reverse($this->listenerTimes, true);
456
            foreach ($this->listenerTimes as $listener => $timeTaken) {
457
                echo "\t$listener: ".round(($timeTaken), 4).' secs'.PHP_EOL;
458
            }
459
460
            echo "\t*** END SNIFF PROCESSING REPORT ***".PHP_EOL;
461
        }
462
463
    }//end process()
464
465
466
    /**
467
     * Tokenizes the file and prepares it for the test run.
468
     *
469
     * @return void
470
     */
471
    public function parse()
472
    {
473
        if (empty($this->tokens) === false) {
474
            // File has already been parsed.
475
            return;
476
        }
477
478
        try {
479
            $tokenizerClass  = 'Symplify\PHP7_CodeSniffer\Tokenizers\\'.$this->tokenizerType;
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
480
            $this->tokenizer = new $tokenizerClass($this->content, $this->config, $this->eolChar);
481
            $this->tokens    = $this->tokenizer->getTokens();
482
        } catch (TokenizerException $e) {
483
            $this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception');
484
            if (PHP_CodeSniffer_VERBOSITY > 0
485
                || (PHP_CodeSniffer_CBF === true && $this->config->stdin === false)
0 ignored issues
show
Documentation introduced by
The property stdin 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...
486
            ) {
487
                echo "[$this->tokenizerType => tokenizer error]... ";
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
488
                if (PHP_CodeSniffer_VERBOSITY > 1) {
489
                    echo PHP_EOL;
490
                }
491
            }
492
493
            return;
494
        }
495
496
        $this->numTokens = count($this->tokens);
497
498
        // Check for mixed line endings as these can cause tokenizer errors and we
499
        // should let the user know that the results they get may be incorrect.
500
        // This is done by removing all backslashes, removing the newline char we
501
        // detected, then converting newlines chars into text. If any backslashes
502
        // are left at the end, we have additional newline chars in use.
503
        $contents = str_replace('\\', '', $this->content);
504
        $contents = str_replace($this->eolChar, '', $contents);
505
        $contents = str_replace("\n", '\n', $contents);
506
        $contents = str_replace("\r", '\r', $contents);
507
        if (strpos($contents, '\\') !== false) {
508
            $error = 'File has mixed line endings; this may cause incorrect results';
509
            $this->addWarningOnLine($error, 1, 'Internal.LineEndings.Mixed');
510
        }
511
512
        if (PHP_CodeSniffer_VERBOSITY > 0
513
            || (PHP_CodeSniffer_CBF === true && $this->config->stdin === false)
0 ignored issues
show
Documentation introduced by
The property stdin 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...
514
        ) {
515
            if ($this->numTokens === 0) {
516
                $numLines = 0;
517
            } else {
518
                $numLines = $this->tokens[($this->numTokens - 1)]['line'];
519
            }
520
521
            echo "[$this->tokenizerType => $this->numTokens tokens in $numLines lines]... ";
0 ignored issues
show
Bug introduced by
The property tokenizerType does not seem to exist. Did you mean tokenizer?

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...
522
            if (PHP_CodeSniffer_VERBOSITY > 1) {
523
                echo PHP_EOL;
524
            }
525
        }
526
527
    }//end parse()
528
529
530
    /**
531
     * Returns the token stack for this file.
532
     *
533
     * @return array
534
     */
535
    public function getTokens()
536
    {
537
        return $this->tokens;
538
539
    }//end getTokens()
540
541
542
    /**
543
     * Remove vars stored in this file that are no longer required.
544
     *
545
     * @return void
546
     */
547
    public function cleanUp()
548
    {
549
        $this->listenerTimes = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $listenerTimes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
550
        $this->content       = null;
551
        $this->tokens        = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $tokens.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
552
        $this->tokenizer     = null;
553
        $this->fixer         = null;
554
555
    }//end cleanUp()
556
557
558
    /**
559
     * Records an error against a specific token in the file.
560
     *
561
     * @param string  $error    The error message.
562
     * @param int     $stackPtr The stack position where the error occurred.
563
     * @param string  $code     A violation code unique to the sniff message.
564
     * @param array   $data     Replacements for the error message.
565
     * @param int     $severity The severity level for this error. A value of 0
566
     *                          will be converted into the default severity level.
567
     * @param boolean $fixable  Can the error be fixed by the sniff?
568
     *
569
     * @return boolean
570
     */
571 View Code Duplication
    public function addError(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
572
        $error,
573
        $stackPtr,
574
        $code,
575
        $data=array(),
576
        $severity=0,
577
        $fixable=false
578
    ) {
579
        if ($stackPtr === null) {
580
            $line   = 1;
581
            $column = 1;
582
        } else {
583
            $line   = $this->tokens[$stackPtr]['line'];
584
            $column = $this->tokens[$stackPtr]['column'];
585
        }
586
587
        return $this->addMessage(true, $error, $line, $column, $code, $data, $severity, $fixable);
588
589
    }//end addError()
590
591
592
    /**
593
     * Records a warning against a specific token in the file.
594
     *
595
     * @param string  $warning  The error message.
596
     * @param int     $stackPtr The stack position where the error occurred.
597
     * @param string  $code     A violation code unique to the sniff message.
598
     * @param array   $data     Replacements for the warning message.
599
     * @param int     $severity The severity level for this warning. A value of 0
600
     *                          will be converted into the default severity level.
601
     * @param boolean $fixable  Can the warning be fixed by the sniff?
602
     *
603
     * @return boolean
604
     */
605 View Code Duplication
    public function addWarning(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
606
        $warning,
607
        $stackPtr,
608
        $code,
609
        $data=array(),
610
        $severity=0,
611
        $fixable=false
612
    ) {
613
        if ($stackPtr === null) {
614
            $line   = 1;
615
            $column = 1;
616
        } else {
617
            $line   = $this->tokens[$stackPtr]['line'];
618
            $column = $this->tokens[$stackPtr]['column'];
619
        }
620
621
        return $this->addMessage(false, $warning, $line, $column, $code, $data, $severity, $fixable);
622
623
    }//end addWarning()
624
625
626
    /**
627
     * Records an error against a specific line in the file.
628
     *
629
     * @param string $error    The error message.
630
     * @param int    $line     The line on which the error occurred.
631
     * @param string $code     A violation code unique to the sniff message.
632
     * @param array  $data     Replacements for the error message.
633
     * @param int    $severity The severity level for this error. A value of 0
634
     *                         will be converted into the default severity level.
635
     *
636
     * @return boolean
637
     */
638
    public function addErrorOnLine(
639
        $error,
640
        $line,
641
        $code,
642
        $data=array(),
643
        $severity=0
644
    ) {
645
        return $this->addMessage(true, $error, $line, 1, $code, $data, $severity, false);
646
647
    }//end addErrorOnLine()
648
649
650
    /**
651
     * Records a warning against a specific token in the file.
652
     *
653
     * @param string $warning  The error message.
654
     * @param int    $line     The line on which the warning occurred.
655
     * @param string $code     A violation code unique to the sniff message.
656
     * @param array  $data     Replacements for the warning message.
657
     * @param int    $severity The severity level for this warning. A value of 0 will
658
     *                         will be converted into the default severity level.
659
     *
660
     * @return boolean
661
     */
662
    public function addWarningOnLine(
663
        $warning,
664
        $line,
665
        $code,
666
        $data=array(),
667
        $severity=0
668
    ) {
669
        return $this->addMessage(false, $warning, $line, 1, $code, $data, $severity, false);
670
671
    }//end addWarningOnLine()
672
673
674
    /**
675
     * Records a fixable error against a specific token in the file.
676
     *
677
     * Returns true if the error was recorded and should be fixed.
678
     *
679
     * @param string $error    The error message.
680
     * @param int    $stackPtr The stack position where the error occurred.
681
     * @param string $code     A violation code unique to the sniff message.
682
     * @param array  $data     Replacements for the error message.
683
     * @param int    $severity The severity level for this error. A value of 0
684
     *                         will be converted into the default severity level.
685
     *
686
     * @return boolean
687
     */
688 View Code Duplication
    public function addFixableError(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
689
        $error,
690
        $stackPtr,
691
        $code,
692
        $data=array(),
693
        $severity=0
694
    ) {
695
        $recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true);
696
        if ($recorded === true && $this->fixer->enabled === true) {
697
            return true;
698
        }
699
700
        return false;
701
702
    }//end addFixableError()
703
704
705
    /**
706
     * Records a fixable warning against a specific token in the file.
707
     *
708
     * Returns true if the warning was recorded and should be fixed.
709
     *
710
     * @param string $warning  The error message.
711
     * @param int    $stackPtr The stack position where the error occurred.
712
     * @param string $code     A violation code unique to the sniff message.
713
     * @param array  $data     Replacements for the warning message.
714
     * @param int    $severity The severity level for this warning. A value of 0
715
     *                         will be converted into the default severity level.
716
     *
717
     * @return boolean
718
     */
719 View Code Duplication
    public function addFixableWarning(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
720
        $warning,
721
        $stackPtr,
722
        $code,
723
        $data=array(),
724
        $severity=0
725
    ) {
726
        $recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true);
727
        if ($recorded === true && $this->fixer->enabled === true) {
728
            return true;
729
        }
730
731
        return false;
732
733
    }//end addFixableWarning()
734
735
736
    /**
737
     * Adds an error to the error stack.
738
     *
739
     * @param boolean $error    Is this an error message?
740
     * @param string  $message  The text of the message.
741
     * @param int     $line     The line on which the message occurred.
742
     * @param int     $column   The column at which the message occurred.
743
     * @param string  $code     A violation code unique to the sniff message.
744
     * @param array   $data     Replacements for the message.
745
     * @param int     $severity The severity level for this message. A value of 0
746
     *                          will be converted into the default severity level.
747
     * @param boolean $fixable  Can the problem be fixed by the sniff?
748
     *
749
     * @return boolean
750
     */
751
    protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable)
752
    {
753
        if (isset($this->tokenizer->ignoredLines[$line]) === true) {
754
            return false;
755
        }
756
757
        $includeAll = true;
758 View Code Duplication
        if ($this->configCache['cache'] === 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...
759
            || $this->configCache['recordErrors'] === false
760
        ) {
761
            $includeAll = false;
762
        }
763
764
        // Work out which sniff generated the message.
765
        $parts = explode('.', $code);
766
        if ($parts[0] === 'Internal') {
767
            // An internal message.
768
            $listenerCode = Util\Common::getSniffCode($this->activeListener);
769
            $sniffCode    = $code;
770
            $checkCodes   = array($sniffCode);
771
        } else {
772
            if ($parts[0] !== $code) {
773
                // The full message code has been passed in.
774
                $sniffCode    = $code;
775
                $listenerCode = substr($sniffCode, 0, strrpos($sniffCode, '.'));
776
            } else {
777
                $listenerCode = Util\Common::getSniffCode($this->activeListener);
778
                $sniffCode    = $listenerCode.'.'.$code;
779
                $parts        = explode('.', $sniffCode);
780
            }
781
782
            $checkCodes = array(
783
                           $sniffCode,
784
                           $parts[0].'.'.$parts[1].'.'.$parts[2],
785
                           $parts[0].'.'.$parts[1],
786
                           $parts[0],
787
                          );
788
        }//end if
789
790
        // Filter out any messages for sniffs that shouldn't have run
791
        // due to the use of the --sniffs command line argument.
792
        if ($includeAll === false
793
            && empty($this->configCache['sniffs']) === false
794
            && in_array($listenerCode, $this->configCache['sniffs']) === false
795
        ) {
796
            return false;
797
        }
798
799
        // If we know this sniff code is being ignored for this file, return early.
800
        foreach ($checkCodes as $checkCode) {
801
            if (isset($this->ignoredCodes[$checkCode]) === true) {
802
                return false;
803
            }
804
        }
805
806
        $oppositeType = 'warning';
807
        if ($error === false) {
808
            $oppositeType = 'error';
809
        }
810
811 View Code Duplication
        foreach ($checkCodes as $checkCode) {
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...
812
            // Make sure this message type has not been set to the opposite message type.
813
            if (isset($this->ruleset->ruleset[$checkCode]['type']) === true
814
                && $this->ruleset->ruleset[$checkCode]['type'] === $oppositeType
815
            ) {
816
                $error = !$error;
817
                break;
818
            }
819
        }
820
821
        if ($error === true) {
822
            $configSeverity = $this->configCache['errorSeverity'];
823
            $messageCount   = &$this->errorCount;
824
            $messages       = &$this->errors;
825
        } else {
826
            $configSeverity = $this->configCache['warningSeverity'];
827
            $messageCount   = &$this->warningCount;
828
            $messages       = &$this->warnings;
829
        }
830
831
        if ($includeAll === false && $configSeverity === 0) {
832
            // Don't bother doing any processing as these messages are just going to
833
            // be hidden in the reports anyway.
834
            return false;
835
        }
836
837
        if ($severity === 0) {
838
            $severity = 5;
839
        }
840
841 View Code Duplication
        foreach ($checkCodes as $checkCode) {
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...
842
            // Make sure we are interested in this severity level.
843
            if (isset($this->ruleset->ruleset[$checkCode]['severity']) === true) {
844
                $severity = $this->ruleset->ruleset[$checkCode]['severity'];
845
                break;
846
            }
847
        }
848
849
        if ($includeAll === false && $configSeverity > $severity) {
850
            return false;
851
        }
852
853
        // Make sure we are not ignoring this file.
854
        foreach ($checkCodes as $checkCode) {
855
            if (isset($this->configCache['ignorePatterns'][$checkCode]) === false) {
856
                continue;
857
            }
858
859
            foreach ($this->configCache['ignorePatterns'][$checkCode] as $pattern => $type) {
860
                // While there is support for a type of each pattern
861
                // (absolute or relative) we don't actually support it here.
862
                $replacements = array(
863
                                 '\\,' => ',',
864
                                 '*'   => '.*',
865
                                );
866
867
                // We assume a / directory separator, as do the exclude rules
868
                // most developers write, so we need a special case for any system
869
                // that is different.
870
                if (DIRECTORY_SEPARATOR === '\\') {
871
                    $replacements['/'] = '\\\\';
872
                }
873
874
                $pattern = '`'.strtr($pattern, $replacements).'`i';
875
                if (preg_match($pattern, $this->path) === 1) {
876
                    $this->ignoredCodes[$checkCode] = true;
877
                    return false;
878
                }
879
            }//end foreach
880
        }//end foreach
881
882
        $messageCount++;
883
        if ($fixable === true) {
884
            $this->fixableCount++;
885
        }
886
887 View Code Duplication
        if ($this->configCache['recordErrors'] === 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...
888
            && $includeAll === false
889
        ) {
890
            return true;
891
        }
892
893
        // Work out the error message.
894
        if (isset($this->ruleset->ruleset[$sniffCode]['message']) === true) {
895
            $message = $this->ruleset->ruleset[$sniffCode]['message'];
896
        }
897
898
        if (empty($data) === false) {
899
            $message = vsprintf($message, $data);
900
        }
901
902
        if (isset($messages[$line]) === false) {
903
            $messages[$line] = array();
904
        }
905
906
        if (isset($messages[$line][$column]) === false) {
907
            $messages[$line][$column] = array();
908
        }
909
910
        $messages[$line][$column][] = array(
911
                                       'message'  => $message,
912
                                       'source'   => $sniffCode,
913
                                       'listener' => $this->activeListener,
914
                                       'severity' => $severity,
915
                                       'fixable'  => $fixable,
916
                                      );
917
918
        if (PHP_CodeSniffer_VERBOSITY > 1
919
            && $this->fixer->enabled === true
920
            && $fixable === true
921
        ) {
922
            @ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
923
            echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL;
924
            ob_start();
925
        }
926
927
        return true;
928
929
    }//end addMessage()
930
931
932
    /**
933
     * Adds an warning to the warning stack.
934
     *
935
     * @param int    $stackPtr The stack position where the metric was recorded.
936
     * @param string $metric   The name of the metric being recorded.
937
     * @param string $value    The value of the metric being recorded.
938
     *
939
     * @return boolean
940
     */
941
    public function recordMetric($stackPtr, $metric, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $stackPtr is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
942
    {
943
        if (isset($this->metrics[$metric]) === false) {
944
            $this->metrics[$metric] = array('values' => array($value => 1));
945
        } else {
946
            if (isset($this->metrics[$metric]['values'][$value]) === false) {
947
                $this->metrics[$metric]['values'][$value] = 1;
948
            } else {
949
                $this->metrics[$metric]['values'][$value]++;
950
            }
951
        }
952
953
        return true;
954
955
    }//end recordMetric()
956
957
958
    /**
959
     * Returns the number of errors raised.
960
     *
961
     * @return int
962
     */
963
    public function getErrorCount()
964
    {
965
        return $this->errorCount;
966
967
    }//end getErrorCount()
968
969
970
    /**
971
     * Returns the number of warnings raised.
972
     *
973
     * @return int
974
     */
975
    public function getWarningCount()
976
    {
977
        return $this->warningCount;
978
979
    }//end getWarningCount()
980
981
982
    /**
983
     * Returns the number of successes recorded.
984
     *
985
     * @return int
986
     */
987
    public function getSuccessCount()
988
    {
989
        return $this->successCount;
0 ignored issues
show
Bug introduced by
The property successCount 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...
990
991
    }//end getSuccessCount()
992
993
994
    /**
995
     * Returns the number of fixable errors/warnings raised.
996
     *
997
     * @return int
998
     */
999
    public function getFixableCount()
1000
    {
1001
        return $this->fixableCount;
1002
1003
    }//end getFixableCount()
1004
1005
1006
    /**
1007
     * Returns the list of ignored lines.
1008
     *
1009
     * @return array
1010
     */
1011
    public function getIgnoredLines()
1012
    {
1013
        return $this->tokenizer->ignoredLines;
1014
1015
    }//end getIgnoredLines()
1016
1017
1018
    /**
1019
     * Returns the errors raised from processing this file.
1020
     *
1021
     * @return array
1022
     */
1023
    public function getErrors()
1024
    {
1025
        return $this->errors;
1026
1027
    }//end getErrors()
1028
1029
1030
    /**
1031
     * Returns the warnings raised from processing this file.
1032
     *
1033
     * @return array
1034
     */
1035
    public function getWarnings()
1036
    {
1037
        return $this->warnings;
1038
1039
    }//end getWarnings()
1040
1041
1042
    /**
1043
     * Returns the metrics found while processing this file.
1044
     *
1045
     * @return array
1046
     */
1047
    public function getMetrics()
1048
    {
1049
        return $this->metrics;
1050
1051
    }//end getMetrics()
1052
1053
1054
    /**
1055
     * Returns the absolute filename of this file.
1056
     *
1057
     * @return string
1058
     */
1059
    public function getFilename()
1060
    {
1061
        return $this->path;
1062
1063
    }//end getFilename()
1064
1065
1066
    /**
1067
     * Returns the declaration names for T_CLASS, T_INTERFACE and T_FUNCTION tokens.
1068
     *
1069
     * @param int $stackPtr The position of the declaration token which
1070
     *                      declared the class, interface or function.
1071
     *
1072
     * @return string|null The name of the class, interface or function.
1073
     *                     or NULL if the function is a closure.
1074
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified token is not of type
1075
     *                                   T_FUNCTION, T_CLASS or T_INTERFACE.
1076
     */
1077
    public function getDeclarationName($stackPtr)
1078
    {
1079
        $tokenCode = $this->tokens[$stackPtr]['code'];
1080
        if ($tokenCode !== T_FUNCTION
1081
            && $tokenCode !== T_CLASS
1082
            && $tokenCode !== T_INTERFACE
1083
            && $tokenCode !== T_TRAIT
1084
        ) {
1085
            throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
1086
        }
1087
1088
        if ($tokenCode === T_FUNCTION
1089
            && $this->isAnonymousFunction($stackPtr) === true
1090
        ) {
1091
            return null;
1092
        }
1093
1094
        $content = null;
1095 View Code Duplication
        for ($i = $stackPtr; $i < $this->numTokens; $i++) {
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...
1096
            if ($this->tokens[$i]['code'] === T_STRING) {
1097
                $content = $this->tokens[$i]['content'];
1098
                break;
1099
            }
1100
        }
1101
1102
        return $content;
1103
1104
    }//end getDeclarationName()
1105
1106
1107
    /**
1108
     * Check if the token at the specified position is a anonymous function.
1109
     *
1110
     * @param int $stackPtr The position of the declaration token which
1111
     *                      declared the class, interface or function.
1112
     *
1113
     * @return boolean
1114
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified token is not of type
1115
     *                                   T_FUNCTION
1116
     */
1117
    public function isAnonymousFunction($stackPtr)
1118
    {
1119
        $tokenCode = $this->tokens[$stackPtr]['code'];
1120
        if ($tokenCode !== T_FUNCTION) {
1121
            throw new TokenizerException('Token type is not T_FUNCTION');
1122
        }
1123
1124
        if (isset($this->tokens[$stackPtr]['parenthesis_opener']) === false) {
1125
            // Something is not right with this function.
1126
            return false;
1127
        }
1128
1129
        $name = false;
1130 View Code Duplication
        for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) {
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...
1131
            if ($this->tokens[$i]['code'] === T_STRING) {
1132
                $name = $i;
1133
                break;
1134
            }
1135
        }
1136
1137
        if ($name === false) {
1138
            // No name found.
1139
            return true;
1140
        }
1141
1142
        $open = $this->tokens[$stackPtr]['parenthesis_opener'];
1143
        if ($name > $open) {
1144
            return true;
1145
        }
1146
1147
        return false;
1148
1149
    }//end isAnonymousFunction()
1150
1151
1152
    /**
1153
     * Returns the method parameters for the specified T_FUNCTION token.
1154
     *
1155
     * Each parameter is in the following format:
1156
     *
1157
     * <code>
1158
     *   0 => array(
1159
     *         'name'              => '$var',  // The variable name.
1160
     *         'pass_by_reference' => false,   // Passed by reference.
1161
     *         'type_hint'         => string,  // Type hint for array or custom type
1162
     *        )
1163
     * </code>
1164
     *
1165
     * Parameters with default values have an additional array index of
1166
     * 'default' with the value of the default as a string.
1167
     *
1168
     * @param int $stackPtr The position in the stack of the T_FUNCTION token
1169
     *                      to acquire the parameters for.
1170
     *
1171
     * @return array
1172
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified $stackPtr is not of
1173
     *                                   type T_FUNCTION.
1174
     */
1175
    public function getMethodParameters($stackPtr)
1176
    {
1177
        if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION) {
1178
            throw new TokenizerException('$stackPtr must be of type T_FUNCTION');
1179
        }
1180
1181
        $opener = $this->tokens[$stackPtr]['parenthesis_opener'];
1182
        $closer = $this->tokens[$stackPtr]['parenthesis_closer'];
1183
1184
        $vars            = array();
1185
        $currVar         = null;
1186
        $defaultStart    = null;
1187
        $paramCount      = 0;
1188
        $passByReference = false;
1189
        $variableLength  = false;
1190
        $typeHint        = '';
1191
1192
        for ($i = ($opener + 1); $i <= $closer; $i++) {
1193
            // Check to see if this token has a parenthesis or bracket opener. If it does
1194
            // it's likely to be an array which might have arguments in it. This
1195
            // could cause problems in our parsing below, so lets just skip to the
1196
            // end of it.
1197
            if (isset($this->tokens[$i]['parenthesis_opener']) === true) {
1198
                // Don't do this if it's the close parenthesis for the method.
1199
                if ($i !== $this->tokens[$i]['parenthesis_closer']) {
1200
                    $i = ($this->tokens[$i]['parenthesis_closer'] + 1);
1201
                }
1202
            }
1203
1204
            if (isset($this->tokens[$i]['bracket_opener']) === true) {
1205
                // Don't do this if it's the close parenthesis for the method.
1206
                if ($i !== $this->tokens[$i]['bracket_closer']) {
1207
                    $i = ($this->tokens[$i]['bracket_closer'] + 1);
1208
                }
1209
            }
1210
1211
            switch ($this->tokens[$i]['code']) {
1212
            case T_BITWISE_AND:
1213
                $passByReference = true;
1214
                break;
1215
            case T_VARIABLE:
1216
                $currVar = $i;
1217
                break;
1218
            case T_ELLIPSIS:
1219
                $variableLength = true;
1220
                break;
1221
            case T_ARRAY_HINT:
1222
            case T_CALLABLE:
1223
                $typeHint = $this->tokens[$i]['content'];
1224
                break;
1225
            case T_STRING:
1226
                // This is a string, so it may be a type hint, but it could
1227
                // also be a constant used as a default value.
1228
                $prevComma = false;
1229 View Code Duplication
                for ($t = $i; $t >= $opener; $t--) {
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...
1230
                    if ($this->tokens[$t]['code'] === T_COMMA) {
1231
                        $prevComma = $t;
1232
                        break;
1233
                    }
1234
                }
1235
1236
                if ($prevComma !== false) {
1237
                    $nextEquals = false;
1238 View Code Duplication
                    for ($t = $prevComma; $t < $i; $t++) {
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...
1239
                        if ($this->tokens[$t]['code'] === T_EQUAL) {
1240
                            $nextEquals = $t;
1241
                            break;
1242
                        }
1243
                    }
1244
1245
                    if ($nextEquals !== false) {
1246
                        break;
1247
                    }
1248
                }
1249
1250
                if ($defaultStart === null) {
1251
                    $typeHint .= $this->tokens[$i]['content'];
1252
                }
1253
                break;
1254
            case T_NS_SEPARATOR:
1255
                // Part of a type hint or default value.
1256
                if ($defaultStart === null) {
1257
                    $typeHint .= $this->tokens[$i]['content'];
1258
                }
1259
                break;
1260
            case T_CLOSE_PARENTHESIS:
1261
            case T_COMMA:
1262
                // If it's null, then there must be no parameters for this
1263
                // method.
1264
                if ($currVar === null) {
1265
                    continue;
1266
                }
1267
1268
                $vars[$paramCount]         = array();
1269
                $vars[$paramCount]['name'] = $this->tokens[$currVar]['content'];
1270
1271
                if ($defaultStart !== null) {
1272
                    $vars[$paramCount]['default']
1273
                        = $this->getTokensAsString(
1274
                            $defaultStart,
1275
                            ($i - $defaultStart)
1276
                        );
1277
                }
1278
1279
                $vars[$paramCount]['pass_by_reference'] = $passByReference;
1280
                $vars[$paramCount]['variable_length']   = $variableLength;
1281
                $vars[$paramCount]['type_hint']         = $typeHint;
1282
1283
                // Reset the vars, as we are about to process the next parameter.
1284
                $defaultStart    = null;
1285
                $passByReference = false;
1286
                $variableLength  = false;
1287
                $typeHint        = '';
1288
1289
                $paramCount++;
1290
                break;
1291
            case T_EQUAL:
1292
                $defaultStart = ($i + 1);
1293
                break;
1294
            }//end switch
1295
        }//end for
1296
1297
        return $vars;
1298
1299
    }//end getMethodParameters()
1300
1301
1302
    /**
1303
     * Returns the visibility and implementation properties of a method.
1304
     *
1305
     * The format of the array is:
1306
     * <code>
1307
     *   array(
1308
     *    'scope'           => 'public', // public protected or protected
1309
     *    'scope_specified' => true,     // true is scope keyword was found.
1310
     *    'is_abstract'     => false,    // true if the abstract keyword was found.
1311
     *    'is_final'        => false,    // true if the final keyword was found.
1312
     *    'is_static'       => false,    // true if the static keyword was found.
1313
     *    'is_closure'      => false,    // true if no name is found.
1314
     *   );
1315
     * </code>
1316
     *
1317
     * @param int $stackPtr The position in the stack of the T_FUNCTION token to
1318
     *                      acquire the properties for.
1319
     *
1320
     * @return array
1321
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified position is not a
1322
     *                                   T_FUNCTION token.
1323
     */
1324
    public function getMethodProperties($stackPtr)
1325
    {
1326
        if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION) {
1327
            throw new TokenizerException('$stackPtr must be of type T_FUNCTION');
1328
        }
1329
1330
        $valid = array(
1331
                  T_PUBLIC      => T_PUBLIC,
1332
                  T_PRIVATE     => T_PRIVATE,
1333
                  T_PROTECTED   => T_PROTECTED,
1334
                  T_STATIC      => T_STATIC,
1335
                  T_FINAL       => T_FINAL,
1336
                  T_ABSTRACT    => T_ABSTRACT,
1337
                  T_WHITESPACE  => T_WHITESPACE,
1338
                  T_COMMENT     => T_COMMENT,
1339
                  T_DOC_COMMENT => T_DOC_COMMENT,
1340
                 );
1341
1342
        $scope          = 'public';
1343
        $scopeSpecified = false;
1344
        $isAbstract     = false;
1345
        $isFinal        = false;
1346
        $isStatic       = false;
1347
        $isClosure      = $this->isAnonymousFunction($stackPtr);
1348
1349
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1350
            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1351
                break;
1352
            }
1353
1354
            switch ($this->tokens[$i]['code']) {
1355
            case T_PUBLIC:
1356
                $scope          = 'public';
1357
                $scopeSpecified = true;
1358
                break;
1359
            case T_PRIVATE:
1360
                $scope          = 'private';
1361
                $scopeSpecified = true;
1362
                break;
1363
            case T_PROTECTED:
1364
                $scope          = 'protected';
1365
                $scopeSpecified = true;
1366
                break;
1367
            case T_ABSTRACT:
1368
                $isAbstract = true;
1369
                break;
1370
            case T_FINAL:
1371
                $isFinal = true;
1372
                break;
1373
            case T_STATIC:
1374
                $isStatic = true;
1375
                break;
1376
            }//end switch
1377
        }//end for
1378
1379
        return array(
1380
                'scope'           => $scope,
1381
                'scope_specified' => $scopeSpecified,
1382
                'is_abstract'     => $isAbstract,
1383
                'is_final'        => $isFinal,
1384
                'is_static'       => $isStatic,
1385
                'is_closure'      => $isClosure,
1386
               );
1387
1388
    }//end getMethodProperties()
1389
1390
1391
    /**
1392
     * Returns the visibility and implementation properties of the class member
1393
     * variable found at the specified position in the stack.
1394
     *
1395
     * The format of the array is:
1396
     *
1397
     * <code>
1398
     *   array(
1399
     *    'scope'       => 'public', // public protected or protected
1400
     *    'is_static'   => false,    // true if the static keyword was found.
1401
     *   );
1402
     * </code>
1403
     *
1404
     * @param int $stackPtr The position in the stack of the T_VARIABLE token to
1405
     *                      acquire the properties for.
1406
     *
1407
     * @return array
1408
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified position is not a
1409
     *                                   T_VARIABLE token, or if the position is not
1410
     *                                   a class member variable.
1411
     */
1412
    public function getMemberProperties($stackPtr)
1413
    {
1414
        if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) {
1415
            throw new TokenizerException('$stackPtr must be of type T_VARIABLE');
1416
        }
1417
1418
        $conditions = array_keys($this->tokens[$stackPtr]['conditions']);
1419
        $ptr        = array_pop($conditions);
1420
        if (isset($this->tokens[$ptr]) === false
1421
            || ($this->tokens[$ptr]['code'] !== T_CLASS
1422
            && $this->tokens[$ptr]['code'] !== T_TRAIT)
1423
        ) {
1424
            if (isset($this->tokens[$ptr]) === true
1425
                && $this->tokens[$ptr]['code'] === T_INTERFACE
1426
            ) {
1427
                // T_VARIABLEs in interfaces can actually be method arguments
1428
                // but they wont be seen as being inside the method because there
1429
                // are no scope openers and closers for abstract methods. If it is in
1430
                // parentheses, we can be pretty sure it is a method argument.
1431
                if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
1432
                    || empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
1433
                ) {
1434
                    $error = 'Possible parse error: interfaces may not include member vars';
1435
                    $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
1436
                    return array();
1437
                }
1438
            } else {
1439
                throw new TokenizerException('$stackPtr is not a class member var');
1440
            }
1441
        }
1442
1443
        $valid = array(
1444
                  T_PUBLIC      => T_PUBLIC,
1445
                  T_PRIVATE     => T_PRIVATE,
1446
                  T_PROTECTED   => T_PROTECTED,
1447
                  T_STATIC      => T_STATIC,
1448
                  T_WHITESPACE  => T_WHITESPACE,
1449
                  T_COMMENT     => T_COMMENT,
1450
                  T_DOC_COMMENT => T_DOC_COMMENT,
1451
                  T_VARIABLE    => T_VARIABLE,
1452
                  T_COMMA       => T_COMMA,
1453
                 );
1454
1455
        $scope          = 'public';
1456
        $scopeSpecified = false;
1457
        $isStatic       = false;
1458
1459
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1460
            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1461
                break;
1462
            }
1463
1464
            switch ($this->tokens[$i]['code']) {
1465
            case T_PUBLIC:
1466
                $scope          = 'public';
1467
                $scopeSpecified = true;
1468
                break;
1469
            case T_PRIVATE:
1470
                $scope          = 'private';
1471
                $scopeSpecified = true;
1472
                break;
1473
            case T_PROTECTED:
1474
                $scope          = 'protected';
1475
                $scopeSpecified = true;
1476
                break;
1477
            case T_STATIC:
1478
                $isStatic = true;
1479
                break;
1480
            }
1481
        }//end for
1482
1483
        return array(
1484
                'scope'           => $scope,
1485
                'scope_specified' => $scopeSpecified,
1486
                'is_static'       => $isStatic,
1487
               );
1488
1489
    }//end getMemberProperties()
1490
1491
1492
    /**
1493
     * Returns the visibility and implementation properties of a class.
1494
     *
1495
     * The format of the array is:
1496
     * <code>
1497
     *   array(
1498
     *    'is_abstract' => false, // true if the abstract keyword was found.
1499
     *    'is_final'    => false, // true if the final keyword was found.
1500
     *   );
1501
     * </code>
1502
     *
1503
     * @param int $stackPtr The position in the stack of the T_CLASS token to
1504
     *                      acquire the properties for.
1505
     *
1506
     * @return array
1507
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified position is not a
1508
     *                                   T_CLASS token.
1509
     */
1510
    public function getClassProperties($stackPtr)
1511
    {
1512
        if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
1513
            throw new TokenizerException('$stackPtr must be of type T_CLASS');
1514
        }
1515
1516
        $valid = array(
1517
                  T_FINAL       => T_FINAL,
1518
                  T_ABSTRACT    => T_ABSTRACT,
1519
                  T_WHITESPACE  => T_WHITESPACE,
1520
                  T_COMMENT     => T_COMMENT,
1521
                  T_DOC_COMMENT => T_DOC_COMMENT,
1522
                 );
1523
1524
        $isAbstract = false;
1525
        $isFinal    = false;
1526
1527
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1528
            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1529
                break;
1530
            }
1531
1532
            switch ($this->tokens[$i]['code']) {
1533
            case T_ABSTRACT:
1534
                $isAbstract = true;
1535
                break;
1536
1537
            case T_FINAL:
1538
                $isFinal = true;
1539
                break;
1540
            }
1541
        }//end for
1542
1543
        return array(
1544
                'is_abstract' => $isAbstract,
1545
                'is_final'    => $isFinal,
1546
               );
1547
1548
    }//end getClassProperties()
1549
1550
1551
    /**
1552
     * Determine if the passed token is a reference operator.
1553
     *
1554
     * Returns true if the specified token position represents a reference.
1555
     * Returns false if the token represents a bitwise operator.
1556
     *
1557
     * @param int $stackPtr The position of the T_BITWISE_AND token.
1558
     *
1559
     * @return boolean
1560
     */
1561
    public function isReference($stackPtr)
1562
    {
1563
        if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
1564
            return false;
1565
        }
1566
1567
        $tokenBefore = $this->findPrevious(
1568
            Util\Tokens::$emptyTokens,
1569
            ($stackPtr - 1),
1570
            null,
1571
            true
1572
        );
1573
1574
        if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) {
1575
            // Function returns a reference.
1576
            return true;
1577
        }
1578
1579
        if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
1580
            // Inside a foreach loop, this is a reference.
1581
            return true;
1582
        }
1583
1584
        if ($this->tokens[$tokenBefore]['code'] === T_AS) {
1585
            // Inside a foreach loop, this is a reference.
1586
            return true;
1587
        }
1588
1589
        if ($this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY) {
1590
            // Inside an array declaration, this is a reference.
1591
            return true;
1592
        }
1593
1594
        if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) {
1595
            // This is directly after an assignment. It's a reference. Even if
1596
            // it is part of an operation, the other tests will handle it.
1597
            return true;
1598
        }
1599
1600
        if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) {
1601
            $brackets    = $this->tokens[$stackPtr]['nested_parenthesis'];
1602
            $lastBracket = array_pop($brackets);
1603
            if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) {
1604
                $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']];
1605
                if ($owner['code'] === T_FUNCTION
1606
                    || $owner['code'] === T_CLOSURE
1607
                    || $owner['code'] === T_ARRAY
1608
                ) {
1609
                    // Inside a function or array declaration, this is a reference.
1610
                    return true;
1611
                }
1612
            } else {
1613
                $prev = false;
1614
                for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
1615
                    if ($this->tokens[$t]['code'] !== T_WHITESPACE) {
1616
                        $prev = $t;
1617
                        break;
1618
                    }
1619
                }
1620
1621
                if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) {
1622
                    return true;
1623
                }
1624
            }//end if
1625
        }//end if
1626
1627
        $tokenAfter = $this->findNext(
1628
            Util\Tokens::$emptyTokens,
1629
            ($stackPtr + 1),
1630
            null,
1631
            true
1632
        );
1633
1634
        if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE
1635
            && ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
1636
            || $this->tokens[$tokenBefore]['code'] === T_COMMA)
1637
        ) {
1638
            return true;
1639
        }
1640
1641
        return false;
1642
1643
    }//end isReference()
1644
1645
1646
    /**
1647
     * Returns the content of the tokens from the specified start position in
1648
     * the token stack for the specified length.
1649
     *
1650
     * @param int $start  The position to start from in the token stack.
1651
     * @param int $length The length of tokens to traverse from the start pos.
1652
     *
1653
     * @return string The token contents.
1654
     */
1655
    public function getTokensAsString($start, $length)
1656
    {
1657
        $str = '';
1658
        $end = ($start + $length);
1659
        if ($end > $this->numTokens) {
1660
            $end = $this->numTokens;
1661
        }
1662
1663
        for ($i = $start; $i < $end; $i++) {
1664
            $str .= $this->tokens[$i]['content'];
1665
        }
1666
1667
        return $str;
1668
1669
    }//end getTokensAsString()
1670
1671
1672
    /**
1673
     * Returns the position of the previous specified token(s).
1674
     *
1675
     * If a value is specified, the previous token of the specified type(s)
1676
     * containing the specified value will be returned.
1677
     *
1678
     * Returns false if no token can be found.
1679
     *
1680
     * @param int|array $types   The type(s) of tokens to search for.
1681
     * @param int       $start   The position to start searching from in the
1682
     *                           token stack.
1683
     * @param int       $end     The end position to fail if no token is found.
1684
     *                           if not specified or null, end will default to
1685
     *                           the start of the token stack.
1686
     * @param bool      $exclude If true, find the previous token that is NOT of
1687
     *                           the types specified in $types.
1688
     * @param string    $value   The value that the token(s) must be equal to.
1689
     *                           If value is omitted, tokens with any value will
1690
     *                           be returned.
1691
     * @param bool      $local   If true, tokens outside the current statement
1692
     *                           will not be checked. IE. checking will stop
1693
     *                           at the previous semi-colon found.
1694
     *
1695
     * @return int|bool
1696
     * @see    findNext()
1697
     */
1698
    public function findPrevious(
1699
        $types,
1700
        $start,
1701
        $end=null,
1702
        $exclude=false,
1703
        $value=null,
1704
        $local=false
1705
    ) {
1706
        $types = (array) $types;
1707
1708
        if ($end === null) {
1709
            $end = 0;
1710
        }
1711
1712
        for ($i = $start; $i >= $end; $i--) {
1713
            $found = (bool) $exclude;
1714 View Code Duplication
            foreach ($types as $type) {
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...
1715
                if ($this->tokens[$i]['code'] === $type) {
1716
                    $found = !$exclude;
1717
                    break;
1718
                }
1719
            }
1720
1721 View Code Duplication
            if ($found === 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...
1722
                if ($value === null) {
1723
                    return $i;
1724
                } else if ($this->tokens[$i]['content'] === $value) {
1725
                    return $i;
1726
                }
1727
            }
1728
1729 View Code Duplication
            if ($local === 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...
1730
                if (isset($this->tokens[$i]['scope_opener']) === true
1731
                    && $i === $this->tokens[$i]['scope_closer']
1732
                ) {
1733
                    $i = $this->tokens[$i]['scope_opener'];
1734
                } else if (isset($this->tokens[$i]['bracket_opener']) === true
1735
                    && $i === $this->tokens[$i]['bracket_closer']
1736
                ) {
1737
                    $i = $this->tokens[$i]['bracket_opener'];
1738
                } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
1739
                    && $i === $this->tokens[$i]['parenthesis_closer']
1740
                ) {
1741
                    $i = $this->tokens[$i]['parenthesis_opener'];
1742
                } else if ($this->tokens[$i]['code'] === T_SEMICOLON) {
1743
                    break;
1744
                }
1745
            }
1746
        }//end for
1747
1748
        return false;
1749
1750
    }//end findPrevious()
1751
1752
1753
    /**
1754
     * Returns the position of the next specified token(s).
1755
     *
1756
     * If a value is specified, the next token of the specified type(s)
1757
     * containing the specified value will be returned.
1758
     *
1759
     * Returns false if no token can be found.
1760
     *
1761
     * @param int|array $types   The type(s) of tokens to search for.
1762
     * @param int       $start   The position to start searching from in the
1763
     *                           token stack.
1764
     * @param int       $end     The end position to fail if no token is found.
1765
     *                           if not specified or null, end will default to
1766
     *                           the end of the token stack.
1767
     * @param bool      $exclude If true, find the next token that is NOT of
1768
     *                           a type specified in $types.
1769
     * @param string    $value   The value that the token(s) must be equal to.
1770
     *                           If value is omitted, tokens with any value will
1771
     *                           be returned.
1772
     * @param bool      $local   If true, tokens outside the current statement
1773
     *                           will not be checked. i.e., checking will stop
1774
     *                           at the next semi-colon found.
1775
     *
1776
     * @return int|bool
1777
     * @see    findPrevious()
1778
     */
1779
    public function findNext(
1780
        $types,
1781
        $start,
1782
        $end=null,
1783
        $exclude=false,
1784
        $value=null,
1785
        $local=false
1786
    ) {
1787
        $types = (array) $types;
1788
1789
        if ($end === null || $end > $this->numTokens) {
1790
            $end = $this->numTokens;
1791
        }
1792
1793
        for ($i = $start; $i < $end; $i++) {
1794
            $found = (bool) $exclude;
1795 View Code Duplication
            foreach ($types as $type) {
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...
1796
                if ($this->tokens[$i]['code'] === $type) {
1797
                    $found = !$exclude;
1798
                    break;
1799
                }
1800
            }
1801
1802 View Code Duplication
            if ($found === 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...
1803
                if ($value === null) {
1804
                    return $i;
1805
                } else if ($this->tokens[$i]['content'] === $value) {
1806
                    return $i;
1807
                }
1808
            }
1809
1810
            if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
1811
                break;
1812
            }
1813
        }//end for
1814
1815
        return false;
1816
1817
    }//end findNext()
1818
1819
1820
    /**
1821
     * Returns the position of the first non-whitespace token in a statement.
1822
     *
1823
     * @param int       $start  The position to start searching from in the token stack.
1824
     * @param int|array $ignore Token types that should not be considered stop points.
1825
     *
1826
     * @return int
1827
     */
1828
    public function findStartOfStatement($start, $ignore=null)
1829
    {
1830
        $endTokens = Util\Tokens::$blockOpeners;
1831
1832
        $endTokens[T_COLON]            = true;
1833
        $endTokens[T_COMMA]            = true;
1834
        $endTokens[T_DOUBLE_ARROW]     = true;
1835
        $endTokens[T_SEMICOLON]        = true;
1836
        $endTokens[T_OPEN_TAG]         = true;
1837
        $endTokens[T_CLOSE_TAG]        = true;
1838
        $endTokens[T_OPEN_SHORT_ARRAY] = true;
1839
1840 View Code Duplication
        if ($ignore !== null) {
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...
1841
            $ignore = (array) $ignore;
1842
            foreach ($ignore as $code) {
1843
                if (isset($endTokens[$code]) === true) {
1844
                    unset($endTokens[$code]);
1845
                }
1846
            }
1847
        }
1848
1849
        $lastNotEmpty = $start;
1850
1851
        for ($i = $start; $i >= 0; $i--) {
1852
            if (isset($endTokens[$this->tokens[$i]['code']]) === true) {
1853
                // Found the end of the previous statement.
1854
                return $lastNotEmpty;
1855
            }
1856
1857 View Code Duplication
            if (isset($this->tokens[$i]['scope_opener']) === 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...
1858
                && $i === $this->tokens[$i]['scope_closer']
1859
            ) {
1860
                // Found the end of the previous scope block.
1861
                return $lastNotEmpty;
1862
            }
1863
1864
            // Skip nested statements.
1865
            if (isset($this->tokens[$i]['bracket_opener']) === true
1866
                && $i === $this->tokens[$i]['bracket_closer']
1867
            ) {
1868
                $i = $this->tokens[$i]['bracket_opener'];
1869
            } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
1870
                && $i === $this->tokens[$i]['parenthesis_closer']
1871
            ) {
1872
                $i = $this->tokens[$i]['parenthesis_opener'];
1873
            }
1874
1875 View Code Duplication
            if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === 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...
1876
                $lastNotEmpty = $i;
1877
            }
1878
        }//end for
1879
1880
        return 0;
1881
1882
    }//end findStartOfStatement()
1883
1884
1885
    /**
1886
     * Returns the position of the last non-whitespace token in a statement.
1887
     *
1888
     * @param int       $start  The position to start searching from in the token stack.
1889
     * @param int|array $ignore Token types that should not be considered stop points.
1890
     *
1891
     * @return int
1892
     */
1893
    public function findEndOfStatement($start, $ignore=null)
1894
    {
1895
        $endTokens = array(
1896
                      T_COLON                => true,
1897
                      T_COMMA                => true,
1898
                      T_DOUBLE_ARROW         => true,
1899
                      T_SEMICOLON            => true,
1900
                      T_CLOSE_PARENTHESIS    => true,
1901
                      T_CLOSE_SQUARE_BRACKET => true,
1902
                      T_CLOSE_CURLY_BRACKET  => true,
1903
                      T_CLOSE_SHORT_ARRAY    => true,
1904
                      T_OPEN_TAG             => true,
1905
                      T_CLOSE_TAG            => true,
1906
                     );
1907
1908 View Code Duplication
        if ($ignore !== null) {
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...
1909
            $ignore = (array) $ignore;
1910
            foreach ($ignore as $code) {
1911
                if (isset($endTokens[$code]) === true) {
1912
                    unset($endTokens[$code]);
1913
                }
1914
            }
1915
        }
1916
1917
        $lastNotEmpty = $start;
1918
1919
        for ($i = $start; $i < $this->numTokens; $i++) {
1920
            if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) {
1921
                // Found the end of the statement.
1922
                if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1923
                    || $this->tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
1924
                    || $this->tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
1925
                    || $this->tokens[$i]['code'] === T_OPEN_TAG
1926
                    || $this->tokens[$i]['code'] === T_CLOSE_TAG
1927
                ) {
1928
                    return $lastNotEmpty;
1929
                }
1930
1931
                return $i;
1932
            }
1933
1934
            // Skip nested statements.
1935 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...
1936
                && ($i === $this->tokens[$i]['scope_opener']
1937
                || $i === $this->tokens[$i]['scope_condition'])
1938
            ) {
1939
                $i = $this->tokens[$i]['scope_closer'];
1940
            } else if (isset($this->tokens[$i]['bracket_closer']) === true
1941
                && $i === $this->tokens[$i]['bracket_opener']
1942
            ) {
1943
                $i = $this->tokens[$i]['bracket_closer'];
1944
            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
1945
                && $i === $this->tokens[$i]['parenthesis_opener']
1946
            ) {
1947
                $i = $this->tokens[$i]['parenthesis_closer'];
1948
            }
1949
1950 View Code Duplication
            if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === 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...
1951
                $lastNotEmpty = $i;
1952
            }
1953
        }//end for
1954
1955
        return ($this->numTokens - 1);
1956
1957
    }//end findEndOfStatement()
1958
1959
1960
    /**
1961
     * Returns the position of the first token on a line, matching given type.
1962
     *
1963
     * Returns false if no token can be found.
1964
     *
1965
     * @param int|array $types   The type(s) of tokens to search for.
1966
     * @param int       $start   The position to start searching from in the
1967
     *                           token stack. The first token matching on
1968
     *                           this line before this token will be returned.
1969
     * @param bool      $exclude If true, find the token that is NOT of
1970
     *                           the types specified in $types.
1971
     * @param string    $value   The value that the token must be equal to.
1972
     *                           If value is omitted, tokens with any value will
1973
     *                           be returned.
1974
     *
1975
     * @return int | bool
1976
     */
1977
    public function findFirstOnLine($types, $start, $exclude=false, $value=null)
1978
    {
1979
        if (is_array($types) === false) {
1980
            $types = array($types);
1981
        }
1982
1983
        $foundToken = false;
1984
1985
        for ($i = $start; $i >= 0; $i--) {
1986
            if ($this->tokens[$i]['line'] < $this->tokens[$start]['line']) {
1987
                break;
1988
            }
1989
1990
            $found = $exclude;
1991
            foreach ($types as $type) {
1992
                if ($exclude === false) {
1993
                    if ($this->tokens[$i]['code'] === $type) {
1994
                        $found = true;
1995
                        break;
1996
                    }
1997
                } else {
1998
                    if ($this->tokens[$i]['code'] === $type) {
1999
                        $found = false;
2000
                        break;
2001
                    }
2002
                }
2003
            }
2004
2005
            if ($found === true) {
2006
                if ($value === null) {
2007
                    $foundToken = $i;
2008
                } else if ($this->tokens[$i]['content'] === $value) {
2009
                    $foundToken = $i;
2010
                }
2011
            }
2012
        }//end for
2013
2014
        return $foundToken;
2015
2016
    }//end findFirstOnLine()
2017
2018
2019
    /**
2020
     * Determine if the passed token has a condition of one of the passed types.
2021
     *
2022
     * @param int       $stackPtr The position of the token we are checking.
2023
     * @param int|array $types    The type(s) of tokens to search for.
2024
     *
2025
     * @return boolean
2026
     */
2027
    public function hasCondition($stackPtr, $types)
2028
    {
2029
        // Check for the existence of the token.
2030
        if (isset($this->tokens[$stackPtr]) === false) {
2031
            return false;
2032
        }
2033
2034
        // Make sure the token has conditions.
2035
        if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2036
            return false;
2037
        }
2038
2039
        $types      = (array) $types;
2040
        $conditions = $this->tokens[$stackPtr]['conditions'];
2041
2042
        foreach ($types as $type) {
2043
            if (in_array($type, $conditions) === true) {
2044
                // We found a token with the required type.
2045
                return true;
2046
            }
2047
        }
2048
2049
        return false;
2050
2051
    }//end hasCondition()
2052
2053
2054
    /**
2055
     * Return the position of the condition for the passed token.
2056
     *
2057
     * Returns FALSE if the token does not have the condition.
2058
     *
2059
     * @param int $stackPtr The position of the token we are checking.
2060
     * @param int $type     The type of token to search for.
2061
     *
2062
     * @return int
2063
     */
2064
    public function getCondition($stackPtr, $type)
2065
    {
2066
        // Check for the existence of the token.
2067
        if (isset($this->tokens[$stackPtr]) === false) {
2068
            return false;
2069
        }
2070
2071
        // Make sure the token has conditions.
2072
        if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2073
            return false;
2074
        }
2075
2076
        $conditions = $this->tokens[$stackPtr]['conditions'];
2077
        foreach ($conditions as $token => $condition) {
2078
            if ($condition === $type) {
2079
                return $token;
2080
            }
2081
        }
2082
2083
        return false;
2084
2085
    }//end getCondition()
2086
2087
2088
    /**
2089
     * Returns the name of the class that the specified class extends.
2090
     *
2091
     * Returns FALSE on error or if there is no extended class name.
2092
     *
2093
     * @param int $stackPtr The stack position of the class.
2094
     *
2095
     * @return string
2096
     */
2097
    public function findExtendedClassName($stackPtr)
2098
    {
2099
        // Check for the existence of the token.
2100
        if (isset($this->tokens[$stackPtr]) === false) {
2101
            return false;
2102
        }
2103
2104
        if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
2105
            return false;
2106
        }
2107
2108
        if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
2109
            return false;
2110
        }
2111
2112
        $classCloserIndex = $this->tokens[$stackPtr]['scope_closer'];
2113
        $extendsIndex     = $this->findNext(T_EXTENDS, $stackPtr, $classCloserIndex);
2114
        if (false === $extendsIndex) {
2115
            return false;
2116
        }
2117
2118
        $find = array(
2119
                 T_NS_SEPARATOR,
2120
                 T_STRING,
2121
                 T_WHITESPACE,
2122
                );
2123
2124
        $end  = $this->findNext($find, ($extendsIndex + 1), $classCloserIndex, true);
2125
        $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
2126
        $name = trim($name);
2127
2128
        if ($name === '') {
2129
            return false;
2130
        }
2131
2132
        return $name;
2133
2134
    }//end findExtendedClassName()
2135
2136
2137
}//end class
2138