Completed
Push — master ( 21dc38...17de5c )
by Tomáš
10s
created

File::getWarningCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 9.4285
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * 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
        $foundCode        = false;
301
        $listenerIgnoreTo = array();
302
        $inTests          = defined('Symplify\PHP7_CodeSniffer_IN_TESTS');
303
304
        // Foreach of the listeners that have registered to listen for this
305
        // token, get them to process it.
306
        foreach ($this->tokens as $stackPtr => $token) {
307
            // Check for ignored lines.
308
            if ($token['code'] === T_COMMENT
309
                || $token['code'] === T_DOC_COMMENT_TAG
310
                || ($inTests === true && $token['code'] === T_INLINE_HTML)
311
            ) {
312
                if (strpos($token['content'], '@codingStandards') !== false) {
313
                    if (strpos($token['content'], '@codingStandardsIgnoreFile') !== false) {
314
                        // Ignoring the whole file, just a little late.
315
                        $this->errors       = array();
316
                        $this->warnings     = array();
317
                        $this->errorCount   = 0;
318
                        $this->warningCount = 0;
319
                        $this->fixableCount = 0;
320
                        return;
321
                    } else if (strpos($token['content'], '@codingStandardsChangeSetting') !== false) {
322
                        $start   = strpos($token['content'], '@codingStandardsChangeSetting');
323
                        $comment = substr($token['content'], ($start + 30));
324
                        $parts   = explode(' ', $comment);
325
                        if ($parts >= 3) {
326
                            $sniffParts = explode('.', $parts[0]);
327
                            if ($sniffParts >= 3) {
328
                                // If the sniff code is not know to us, it has not been registered in this run.
329
                                // But don't throw an error as it could be there for a different standard to use.
330
                                if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
331
                                    $listenerClass = $this->ruleset->sniffCodes[$parts[0]];
332
                                    $this->ruleset->setSniffProperty($listenerClass, $parts[1], $parts[2]);
333
                                }
334
                            }
335
                        }
336
                    }//end if
337
                }//end if
338
            }//end if
339
340
            if ($token['code'] !== T_INLINE_HTML) {
341
                $foundCode = true;
342
            }
343
344
            if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
345
                continue;
346
            }
347
348
            foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
349
                if (isset($this->ignoredListeners[$listenerData['class']]) === true
350
                    || (isset($listenerIgnoreTo[$listenerData['class']]) === true
351
                    && $listenerIgnoreTo[$listenerData['class']] > $stackPtr)
352
                ) {
353
                    // This sniff is ignoring past this token, or the whole file.
354
                    continue;
355
                }
356
357
                // Make sure this sniff supports the tokenizer
358
                // we are currently using.
359
                $class = $listenerData['class'];
360
361
                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...
362
                    continue;
363
                }
364
365
                // If the file path matches one of our ignore patterns, skip it.
366
                // While there is support for a type of each pattern
367
                // (absolute or relative) we don't actually support it here.
368 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...
369
                    // We assume a / directory separator, as do the exclude rules
370
                    // most developers write, so we need a special case for any system
371
                    // that is different.
372
                    if (DIRECTORY_SEPARATOR === '\\') {
373
                        $pattern = str_replace('/', '\\\\', $pattern);
374
                    }
375
376
                    $pattern = '`'.$pattern.'`i';
377
                    if (preg_match($pattern, $this->path) === 1) {
378
                        $this->ignoredListeners[$class] = true;
379
                        continue(2);
380
                    }
381
                }
382
383
                // If the file path does not match one of our include patterns, skip it.
384
                // While there is support for a type of each pattern
385
                // (absolute or relative) we don't actually support it here.
386 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...
387
                    // We assume a / directory separator, as do the exclude rules
388
                    // most developers write, so we need a special case for any system
389
                    // that is different.
390
                    if (DIRECTORY_SEPARATOR === '\\') {
391
                        $pattern = str_replace('/', '\\\\', $pattern);
392
                    }
393
394
                    $pattern = '`'.$pattern.'`i';
395
                    if (preg_match($pattern, $this->path) !== 1) {
396
                        $this->ignoredListeners[$class] = true;
397
                        continue(2);
398
                    }
399
                }
400
401
                $this->activeListener = $class;
402
403
                $ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
404
                if ($ignoreTo !== null) {
405
                    $listenerIgnoreTo[$this->activeListener] = $ignoreTo;
406
                }
407
408
                $this->activeListener = '';
409
            }//end foreach
410
        }//end foreach
411
412
        // If short open tags are off but the file being checked uses
413
        // short open tags, the whole content will be inline HTML
414
        // and nothing will be checked. So try and handle this case.
415
        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...
416
            $shortTags = (bool) ini_get('short_open_tag');
417
            if ($shortTags === false) {
418
                $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.';
419
                $this->addWarning($error, null, 'Internal.NoCodeFound');
420
            }
421
        }
422
423
    }//end process()
424
425
426
    /**
427
     * Tokenizes the file and prepares it for the test run.
428
     *
429
     * @return void
430
     */
431
    public function parse()
432
    {
433
        if (empty($this->tokens) === false) {
434
            // File has already been parsed.
435
            return;
436
        }
437
438
        try {
439
            $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...
440
            $this->tokenizer = new $tokenizerClass($this->content, $this->config, $this->eolChar);
441
            $this->tokens    = $this->tokenizer->getTokens();
442
        } catch (TokenizerException $e) {
443
            $this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception');
444
            if (PHP_CodeSniffer_VERBOSITY > 0
445
                || (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...
446
            ) {
447
                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...
448
                if (PHP_CodeSniffer_VERBOSITY > 1) {
449
                    echo PHP_EOL;
450
                }
451
            }
452
453
            return;
454
        }
455
456
        $this->numTokens = count($this->tokens);
457
458
        // Check for mixed line endings as these can cause tokenizer errors and we
459
        // should let the user know that the results they get may be incorrect.
460
        // This is done by removing all backslashes, removing the newline char we
461
        // detected, then converting newlines chars into text. If any backslashes
462
        // are left at the end, we have additional newline chars in use.
463
        $contents = str_replace('\\', '', $this->content);
464
        $contents = str_replace($this->eolChar, '', $contents);
465
        $contents = str_replace("\n", '\n', $contents);
466
        $contents = str_replace("\r", '\r', $contents);
467
        if (strpos($contents, '\\') !== false) {
468
            $error = 'File has mixed line endings; this may cause incorrect results';
469
            $this->addWarningOnLine($error, 1, 'Internal.LineEndings.Mixed');
470
        }
471
472
        if (PHP_CodeSniffer_VERBOSITY > 0
473
            || (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...
474
        ) {
475
            if ($this->numTokens === 0) {
476
                $numLines = 0;
477
            } else {
478
                $numLines = $this->tokens[($this->numTokens - 1)]['line'];
479
            }
480
481
            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...
482
            if (PHP_CodeSniffer_VERBOSITY > 1) {
483
                echo PHP_EOL;
484
            }
485
        }
486
487
    }//end parse()
488
489
490
    /**
491
     * Returns the token stack for this file.
492
     *
493
     * @return array
494
     */
495
    public function getTokens()
496
    {
497
        return $this->tokens;
498
499
    }//end getTokens()
500
501
502
    /**
503
     * Remove vars stored in this file that are no longer required.
504
     *
505
     * @return void
506
     */
507
    public function cleanUp()
508
    {
509
        $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...
510
        $this->content       = null;
511
        $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...
512
        $this->tokenizer     = null;
513
        $this->fixer         = null;
514
515
    }//end cleanUp()
516
517
518
    /**
519
     * Records an error against a specific token in the file.
520
     *
521
     * @param string  $error    The error message.
522
     * @param int     $stackPtr The stack position where the error occurred.
523
     * @param string  $code     A violation code unique to the sniff message.
524
     * @param array   $data     Replacements for the error message.
525
     * @param int     $severity The severity level for this error. A value of 0
526
     *                          will be converted into the default severity level.
527
     * @param boolean $fixable  Can the error be fixed by the sniff?
528
     *
529
     * @return boolean
530
     */
531 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...
532
        $error,
533
        $stackPtr,
534
        $code,
535
        $data=array(),
536
        $severity=0,
537
        $fixable=false
538
    ) {
539
        if ($stackPtr === null) {
540
            $line   = 1;
541
            $column = 1;
542
        } else {
543
            $line   = $this->tokens[$stackPtr]['line'];
544
            $column = $this->tokens[$stackPtr]['column'];
545
        }
546
547
        return $this->addMessage(true, $error, $line, $column, $code, $data, $severity, $fixable);
548
549
    }//end addError()
550
551
552
    /**
553
     * Records a warning against a specific token in the file.
554
     *
555
     * @param string  $warning  The error message.
556
     * @param int     $stackPtr The stack position where the error occurred.
557
     * @param string  $code     A violation code unique to the sniff message.
558
     * @param array   $data     Replacements for the warning message.
559
     * @param int     $severity The severity level for this warning. A value of 0
560
     *                          will be converted into the default severity level.
561
     * @param boolean $fixable  Can the warning be fixed by the sniff?
562
     *
563
     * @return boolean
564
     */
565 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...
566
        $warning,
567
        $stackPtr,
568
        $code,
569
        $data=array(),
570
        $severity=0,
571
        $fixable=false
572
    ) {
573
        if ($stackPtr === null) {
574
            $line   = 1;
575
            $column = 1;
576
        } else {
577
            $line   = $this->tokens[$stackPtr]['line'];
578
            $column = $this->tokens[$stackPtr]['column'];
579
        }
580
581
        return $this->addMessage(false, $warning, $line, $column, $code, $data, $severity, $fixable);
582
583
    }//end addWarning()
584
585
586
    /**
587
     * Records an error against a specific line in the file.
588
     *
589
     * @param string $error    The error message.
590
     * @param int    $line     The line on which the error occurred.
591
     * @param string $code     A violation code unique to the sniff message.
592
     * @param array  $data     Replacements for the error message.
593
     * @param int    $severity The severity level for this error. A value of 0
594
     *                         will be converted into the default severity level.
595
     *
596
     * @return boolean
597
     */
598
    public function addErrorOnLine(
599
        $error,
600
        $line,
601
        $code,
602
        $data=array(),
603
        $severity=0
604
    ) {
605
        return $this->addMessage(true, $error, $line, 1, $code, $data, $severity, false);
606
607
    }//end addErrorOnLine()
608
609
610
    /**
611
     * Records a warning against a specific token in the file.
612
     *
613
     * @param string $warning  The error message.
614
     * @param int    $line     The line on which the warning occurred.
615
     * @param string $code     A violation code unique to the sniff message.
616
     * @param array  $data     Replacements for the warning message.
617
     * @param int    $severity The severity level for this warning. A value of 0 will
618
     *                         will be converted into the default severity level.
619
     *
620
     * @return boolean
621
     */
622
    public function addWarningOnLine(
623
        $warning,
624
        $line,
625
        $code,
626
        $data=array(),
627
        $severity=0
628
    ) {
629
        return $this->addMessage(false, $warning, $line, 1, $code, $data, $severity, false);
630
631
    }//end addWarningOnLine()
632
633
634
    /**
635
     * Records a fixable error against a specific token in the file.
636
     *
637
     * Returns true if the error was recorded and should be fixed.
638
     *
639
     * @param string $error    The error message.
640
     * @param int    $stackPtr The stack position where the error occurred.
641
     * @param string $code     A violation code unique to the sniff message.
642
     * @param array  $data     Replacements for the error message.
643
     * @param int    $severity The severity level for this error. A value of 0
644
     *                         will be converted into the default severity level.
645
     *
646
     * @return boolean
647
     */
648 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...
649
        $error,
650
        $stackPtr,
651
        $code,
652
        $data=array(),
653
        $severity=0
654
    ) {
655
        $recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true);
656
        if ($recorded === true && $this->fixer->enabled === true) {
657
            return true;
658
        }
659
660
        return false;
661
662
    }//end addFixableError()
663
664
665
    /**
666
     * Records a fixable warning against a specific token in the file.
667
     *
668
     * Returns true if the warning was recorded and should be fixed.
669
     *
670
     * @param string $warning  The error message.
671
     * @param int    $stackPtr The stack position where the error occurred.
672
     * @param string $code     A violation code unique to the sniff message.
673
     * @param array  $data     Replacements for the warning message.
674
     * @param int    $severity The severity level for this warning. A value of 0
675
     *                         will be converted into the default severity level.
676
     *
677
     * @return boolean
678
     */
679 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...
680
        $warning,
681
        $stackPtr,
682
        $code,
683
        $data=array(),
684
        $severity=0
685
    ) {
686
        $recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true);
687
        if ($recorded === true && $this->fixer->enabled === true) {
688
            return true;
689
        }
690
691
        return false;
692
693
    }//end addFixableWarning()
694
695
696
    /**
697
     * Adds an error to the error stack.
698
     *
699
     * @param boolean $error    Is this an error message?
700
     * @param string  $message  The text of the message.
701
     * @param int     $line     The line on which the message occurred.
702
     * @param int     $column   The column at which the message occurred.
703
     * @param string  $code     A violation code unique to the sniff message.
704
     * @param array   $data     Replacements for the message.
705
     * @param int     $severity The severity level for this message. A value of 0
706
     *                          will be converted into the default severity level.
707
     * @param boolean $fixable  Can the problem be fixed by the sniff?
708
     *
709
     * @return boolean
710
     */
711
    protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable)
712
    {
713
        if (isset($this->tokenizer->ignoredLines[$line]) === true) {
714
            return false;
715
        }
716
717
        $includeAll = true;
718 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...
719
            || $this->configCache['recordErrors'] === false
720
        ) {
721
            $includeAll = false;
722
        }
723
724
        // Work out which sniff generated the message.
725
        $parts = explode('.', $code);
726
        if ($parts[0] === 'Internal') {
727
            // An internal message.
728
            $listenerCode = Util\Common::getSniffCode($this->activeListener);
729
            $sniffCode    = $code;
730
            $checkCodes   = array($sniffCode);
731
        } else {
732
            if ($parts[0] !== $code) {
733
                // The full message code has been passed in.
734
                $sniffCode    = $code;
735
                $listenerCode = substr($sniffCode, 0, strrpos($sniffCode, '.'));
736
            } else {
737
                $listenerCode = Util\Common::getSniffCode($this->activeListener);
738
                $sniffCode    = $listenerCode.'.'.$code;
739
                $parts        = explode('.', $sniffCode);
740
            }
741
742
            $checkCodes = array(
743
                           $sniffCode,
744
                           $parts[0].'.'.$parts[1].'.'.$parts[2],
745
                           $parts[0].'.'.$parts[1],
746
                           $parts[0],
747
                          );
748
        }//end if
749
750
        // Filter out any messages for sniffs that shouldn't have run
751
        // due to the use of the --sniffs command line argument.
752
        if ($includeAll === false
753
            && empty($this->configCache['sniffs']) === false
754
            && in_array($listenerCode, $this->configCache['sniffs']) === false
755
        ) {
756
            return false;
757
        }
758
759
        // If we know this sniff code is being ignored for this file, return early.
760
        foreach ($checkCodes as $checkCode) {
761
            if (isset($this->ignoredCodes[$checkCode]) === true) {
762
                return false;
763
            }
764
        }
765
766
        $oppositeType = 'warning';
767
        if ($error === false) {
768
            $oppositeType = 'error';
769
        }
770
771 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...
772
            // Make sure this message type has not been set to the opposite message type.
773
            if (isset($this->ruleset->ruleset[$checkCode]['type']) === true
774
                && $this->ruleset->ruleset[$checkCode]['type'] === $oppositeType
775
            ) {
776
                $error = !$error;
777
                break;
778
            }
779
        }
780
781
        if ($error === true) {
782
            $configSeverity = $this->configCache['errorSeverity'];
783
            $messageCount   = &$this->errorCount;
784
            $messages       = &$this->errors;
785
        } else {
786
            $configSeverity = $this->configCache['warningSeverity'];
787
            $messageCount   = &$this->warningCount;
788
            $messages       = &$this->warnings;
789
        }
790
791
        if ($includeAll === false && $configSeverity === 0) {
792
            // Don't bother doing any processing as these messages are just going to
793
            // be hidden in the reports anyway.
794
            return false;
795
        }
796
797
        if ($severity === 0) {
798
            $severity = 5;
799
        }
800
801 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...
802
            // Make sure we are interested in this severity level.
803
            if (isset($this->ruleset->ruleset[$checkCode]['severity']) === true) {
804
                $severity = $this->ruleset->ruleset[$checkCode]['severity'];
805
                break;
806
            }
807
        }
808
809
        if ($includeAll === false && $configSeverity > $severity) {
810
            return false;
811
        }
812
813
        // Make sure we are not ignoring this file.
814
        foreach ($checkCodes as $checkCode) {
815
            if (isset($this->configCache['ignorePatterns'][$checkCode]) === false) {
816
                continue;
817
            }
818
819
            foreach ($this->configCache['ignorePatterns'][$checkCode] as $pattern => $type) {
820
                // While there is support for a type of each pattern
821
                // (absolute or relative) we don't actually support it here.
822
                $replacements = array(
823
                                 '\\,' => ',',
824
                                 '*'   => '.*',
825
                                );
826
827
                // We assume a / directory separator, as do the exclude rules
828
                // most developers write, so we need a special case for any system
829
                // that is different.
830
                if (DIRECTORY_SEPARATOR === '\\') {
831
                    $replacements['/'] = '\\\\';
832
                }
833
834
                $pattern = '`'.strtr($pattern, $replacements).'`i';
835
                if (preg_match($pattern, $this->path) === 1) {
836
                    $this->ignoredCodes[$checkCode] = true;
837
                    return false;
838
                }
839
            }//end foreach
840
        }//end foreach
841
842
        $messageCount++;
843
        if ($fixable === true) {
844
            $this->fixableCount++;
845
        }
846
847 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...
848
            && $includeAll === false
849
        ) {
850
            return true;
851
        }
852
853
        // Work out the error message.
854
        if (isset($this->ruleset->ruleset[$sniffCode]['message']) === true) {
855
            $message = $this->ruleset->ruleset[$sniffCode]['message'];
856
        }
857
858
        if (empty($data) === false) {
859
            $message = vsprintf($message, $data);
860
        }
861
862
        if (isset($messages[$line]) === false) {
863
            $messages[$line] = array();
864
        }
865
866
        if (isset($messages[$line][$column]) === false) {
867
            $messages[$line][$column] = array();
868
        }
869
870
        $messages[$line][$column][] = array(
871
                                       'message'  => $message,
872
                                       'source'   => $sniffCode,
873
                                       'listener' => $this->activeListener,
874
                                       'severity' => $severity,
875
                                       'fixable'  => $fixable,
876
                                      );
877
878
        if (PHP_CodeSniffer_VERBOSITY > 1
879
            && $this->fixer->enabled === true
880
            && $fixable === true
881
        ) {
882
            @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...
883
            echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL;
884
            ob_start();
885
        }
886
887
        return true;
888
889
    }//end addMessage()
890
891
892
    /**
893
     * Adds an warning to the warning stack.
894
     *
895
     * @param int    $stackPtr The stack position where the metric was recorded.
896
     * @param string $metric   The name of the metric being recorded.
897
     * @param string $value    The value of the metric being recorded.
898
     *
899
     * @return boolean
900
     */
901
    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...
902
    {
903
        if (isset($this->metrics[$metric]) === false) {
904
            $this->metrics[$metric] = array('values' => array($value => 1));
905
        } else {
906
            if (isset($this->metrics[$metric]['values'][$value]) === false) {
907
                $this->metrics[$metric]['values'][$value] = 1;
908
            } else {
909
                $this->metrics[$metric]['values'][$value]++;
910
            }
911
        }
912
913
        return true;
914
915
    }//end recordMetric()
916
917
918
    /**
919
     * Returns the number of errors raised.
920
     *
921
     * @return int
922
     */
923
    public function getErrorCount()
924
    {
925
        return $this->errorCount;
926
927
    }//end getErrorCount()
928
929
930
    /**
931
     * Returns the number of warnings raised.
932
     *
933
     * @return int
934
     */
935
    public function getWarningCount()
936
    {
937
        return $this->warningCount;
938
939
    }//end getWarningCount()
940
941
942
    /**
943
     * Returns the number of successes recorded.
944
     *
945
     * @return int
946
     */
947
    public function getSuccessCount()
948
    {
949
        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...
950
951
    }//end getSuccessCount()
952
953
954
    /**
955
     * Returns the number of fixable errors/warnings raised.
956
     *
957
     * @return int
958
     */
959
    public function getFixableCount()
960
    {
961
        return $this->fixableCount;
962
963
    }//end getFixableCount()
964
965
966
    /**
967
     * Returns the list of ignored lines.
968
     *
969
     * @return array
970
     */
971
    public function getIgnoredLines()
972
    {
973
        return $this->tokenizer->ignoredLines;
974
975
    }//end getIgnoredLines()
976
977
978
    /**
979
     * Returns the errors raised from processing this file.
980
     *
981
     * @return array
982
     */
983
    public function getErrors()
984
    {
985
        return $this->errors;
986
987
    }//end getErrors()
988
989
990
    /**
991
     * Returns the warnings raised from processing this file.
992
     *
993
     * @return array
994
     */
995
    public function getWarnings()
996
    {
997
        return $this->warnings;
998
999
    }//end getWarnings()
1000
1001
1002
    /**
1003
     * Returns the metrics found while processing this file.
1004
     *
1005
     * @return array
1006
     */
1007
    public function getMetrics()
1008
    {
1009
        return $this->metrics;
1010
1011
    }//end getMetrics()
1012
1013
1014
    /**
1015
     * Returns the absolute filename of this file.
1016
     *
1017
     * @return string
1018
     */
1019
    public function getFilename()
1020
    {
1021
        return $this->path;
1022
1023
    }//end getFilename()
1024
1025
1026
    /**
1027
     * Returns the declaration names for T_CLASS, T_INTERFACE and T_FUNCTION tokens.
1028
     *
1029
     * @param int $stackPtr The position of the declaration token which
1030
     *                      declared the class, interface or function.
1031
     *
1032
     * @return string|null The name of the class, interface or function.
1033
     *                     or NULL if the function is a closure.
1034
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified token is not of type
1035
     *                                   T_FUNCTION, T_CLASS or T_INTERFACE.
1036
     */
1037
    public function getDeclarationName($stackPtr)
1038
    {
1039
        $tokenCode = $this->tokens[$stackPtr]['code'];
1040
        if ($tokenCode !== T_FUNCTION
1041
            && $tokenCode !== T_CLASS
1042
            && $tokenCode !== T_INTERFACE
1043
            && $tokenCode !== T_TRAIT
1044
        ) {
1045
            throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
1046
        }
1047
1048
        if ($tokenCode === T_FUNCTION
1049
            && $this->isAnonymousFunction($stackPtr) === true
1050
        ) {
1051
            return null;
1052
        }
1053
1054
        $content = null;
1055 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...
1056
            if ($this->tokens[$i]['code'] === T_STRING) {
1057
                $content = $this->tokens[$i]['content'];
1058
                break;
1059
            }
1060
        }
1061
1062
        return $content;
1063
1064
    }//end getDeclarationName()
1065
1066
1067
    /**
1068
     * Check if the token at the specified position is a anonymous function.
1069
     *
1070
     * @param int $stackPtr The position of the declaration token which
1071
     *                      declared the class, interface or function.
1072
     *
1073
     * @return boolean
1074
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified token is not of type
1075
     *                                   T_FUNCTION
1076
     */
1077
    public function isAnonymousFunction($stackPtr)
1078
    {
1079
        $tokenCode = $this->tokens[$stackPtr]['code'];
1080
        if ($tokenCode !== T_FUNCTION) {
1081
            throw new TokenizerException('Token type is not T_FUNCTION');
1082
        }
1083
1084
        if (isset($this->tokens[$stackPtr]['parenthesis_opener']) === false) {
1085
            // Something is not right with this function.
1086
            return false;
1087
        }
1088
1089
        $name = false;
1090 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...
1091
            if ($this->tokens[$i]['code'] === T_STRING) {
1092
                $name = $i;
1093
                break;
1094
            }
1095
        }
1096
1097
        if ($name === false) {
1098
            // No name found.
1099
            return true;
1100
        }
1101
1102
        $open = $this->tokens[$stackPtr]['parenthesis_opener'];
1103
        if ($name > $open) {
1104
            return true;
1105
        }
1106
1107
        return false;
1108
1109
    }//end isAnonymousFunction()
1110
1111
1112
    /**
1113
     * Returns the method parameters for the specified T_FUNCTION token.
1114
     *
1115
     * Each parameter is in the following format:
1116
     *
1117
     * <code>
1118
     *   0 => array(
1119
     *         'name'              => '$var',  // The variable name.
1120
     *         'pass_by_reference' => false,   // Passed by reference.
1121
     *         'type_hint'         => string,  // Type hint for array or custom type
1122
     *        )
1123
     * </code>
1124
     *
1125
     * Parameters with default values have an additional array index of
1126
     * 'default' with the value of the default as a string.
1127
     *
1128
     * @param int $stackPtr The position in the stack of the T_FUNCTION token
1129
     *                      to acquire the parameters for.
1130
     *
1131
     * @return array
1132
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified $stackPtr is not of
1133
     *                                   type T_FUNCTION.
1134
     */
1135
    public function getMethodParameters($stackPtr)
1136
    {
1137
        if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION) {
1138
            throw new TokenizerException('$stackPtr must be of type T_FUNCTION');
1139
        }
1140
1141
        $opener = $this->tokens[$stackPtr]['parenthesis_opener'];
1142
        $closer = $this->tokens[$stackPtr]['parenthesis_closer'];
1143
1144
        $vars            = array();
1145
        $currVar         = null;
1146
        $defaultStart    = null;
1147
        $paramCount      = 0;
1148
        $passByReference = false;
1149
        $variableLength  = false;
1150
        $typeHint        = '';
1151
1152
        for ($i = ($opener + 1); $i <= $closer; $i++) {
1153
            // Check to see if this token has a parenthesis or bracket opener. If it does
1154
            // it's likely to be an array which might have arguments in it. This
1155
            // could cause problems in our parsing below, so lets just skip to the
1156
            // end of it.
1157
            if (isset($this->tokens[$i]['parenthesis_opener']) === true) {
1158
                // Don't do this if it's the close parenthesis for the method.
1159
                if ($i !== $this->tokens[$i]['parenthesis_closer']) {
1160
                    $i = ($this->tokens[$i]['parenthesis_closer'] + 1);
1161
                }
1162
            }
1163
1164
            if (isset($this->tokens[$i]['bracket_opener']) === true) {
1165
                // Don't do this if it's the close parenthesis for the method.
1166
                if ($i !== $this->tokens[$i]['bracket_closer']) {
1167
                    $i = ($this->tokens[$i]['bracket_closer'] + 1);
1168
                }
1169
            }
1170
1171
            switch ($this->tokens[$i]['code']) {
1172
            case T_BITWISE_AND:
1173
                $passByReference = true;
1174
                break;
1175
            case T_VARIABLE:
1176
                $currVar = $i;
1177
                break;
1178
            case T_ELLIPSIS:
1179
                $variableLength = true;
1180
                break;
1181
            case T_ARRAY_HINT:
1182
            case T_CALLABLE:
1183
                $typeHint = $this->tokens[$i]['content'];
1184
                break;
1185
            case T_STRING:
1186
                // This is a string, so it may be a type hint, but it could
1187
                // also be a constant used as a default value.
1188
                $prevComma = false;
1189 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...
1190
                    if ($this->tokens[$t]['code'] === T_COMMA) {
1191
                        $prevComma = $t;
1192
                        break;
1193
                    }
1194
                }
1195
1196
                if ($prevComma !== false) {
1197
                    $nextEquals = false;
1198 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...
1199
                        if ($this->tokens[$t]['code'] === T_EQUAL) {
1200
                            $nextEquals = $t;
1201
                            break;
1202
                        }
1203
                    }
1204
1205
                    if ($nextEquals !== false) {
1206
                        break;
1207
                    }
1208
                }
1209
1210
                if ($defaultStart === null) {
1211
                    $typeHint .= $this->tokens[$i]['content'];
1212
                }
1213
                break;
1214
            case T_NS_SEPARATOR:
1215
                // Part of a type hint or default value.
1216
                if ($defaultStart === null) {
1217
                    $typeHint .= $this->tokens[$i]['content'];
1218
                }
1219
                break;
1220
            case T_CLOSE_PARENTHESIS:
1221
            case T_COMMA:
1222
                // If it's null, then there must be no parameters for this
1223
                // method.
1224
                if ($currVar === null) {
1225
                    continue;
1226
                }
1227
1228
                $vars[$paramCount]         = array();
1229
                $vars[$paramCount]['name'] = $this->tokens[$currVar]['content'];
1230
1231
                if ($defaultStart !== null) {
1232
                    $vars[$paramCount]['default']
1233
                        = $this->getTokensAsString(
1234
                            $defaultStart,
1235
                            ($i - $defaultStart)
1236
                        );
1237
                }
1238
1239
                $vars[$paramCount]['pass_by_reference'] = $passByReference;
1240
                $vars[$paramCount]['variable_length']   = $variableLength;
1241
                $vars[$paramCount]['type_hint']         = $typeHint;
1242
1243
                // Reset the vars, as we are about to process the next parameter.
1244
                $defaultStart    = null;
1245
                $passByReference = false;
1246
                $variableLength  = false;
1247
                $typeHint        = '';
1248
1249
                $paramCount++;
1250
                break;
1251
            case T_EQUAL:
1252
                $defaultStart = ($i + 1);
1253
                break;
1254
            }//end switch
1255
        }//end for
1256
1257
        return $vars;
1258
1259
    }//end getMethodParameters()
1260
1261
1262
    /**
1263
     * Returns the visibility and implementation properties of a method.
1264
     *
1265
     * The format of the array is:
1266
     * <code>
1267
     *   array(
1268
     *    'scope'           => 'public', // public protected or protected
1269
     *    'scope_specified' => true,     // true is scope keyword was found.
1270
     *    'is_abstract'     => false,    // true if the abstract keyword was found.
1271
     *    'is_final'        => false,    // true if the final keyword was found.
1272
     *    'is_static'       => false,    // true if the static keyword was found.
1273
     *    'is_closure'      => false,    // true if no name is found.
1274
     *   );
1275
     * </code>
1276
     *
1277
     * @param int $stackPtr The position in the stack of the T_FUNCTION token to
1278
     *                      acquire the properties for.
1279
     *
1280
     * @return array
1281
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified position is not a
1282
     *                                   T_FUNCTION token.
1283
     */
1284
    public function getMethodProperties($stackPtr)
1285
    {
1286
        if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION) {
1287
            throw new TokenizerException('$stackPtr must be of type T_FUNCTION');
1288
        }
1289
1290
        $valid = array(
1291
                  T_PUBLIC      => T_PUBLIC,
1292
                  T_PRIVATE     => T_PRIVATE,
1293
                  T_PROTECTED   => T_PROTECTED,
1294
                  T_STATIC      => T_STATIC,
1295
                  T_FINAL       => T_FINAL,
1296
                  T_ABSTRACT    => T_ABSTRACT,
1297
                  T_WHITESPACE  => T_WHITESPACE,
1298
                  T_COMMENT     => T_COMMENT,
1299
                  T_DOC_COMMENT => T_DOC_COMMENT,
1300
                 );
1301
1302
        $scope          = 'public';
1303
        $scopeSpecified = false;
1304
        $isAbstract     = false;
1305
        $isFinal        = false;
1306
        $isStatic       = false;
1307
        $isClosure      = $this->isAnonymousFunction($stackPtr);
1308
1309
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1310
            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1311
                break;
1312
            }
1313
1314
            switch ($this->tokens[$i]['code']) {
1315
            case T_PUBLIC:
1316
                $scope          = 'public';
1317
                $scopeSpecified = true;
1318
                break;
1319
            case T_PRIVATE:
1320
                $scope          = 'private';
1321
                $scopeSpecified = true;
1322
                break;
1323
            case T_PROTECTED:
1324
                $scope          = 'protected';
1325
                $scopeSpecified = true;
1326
                break;
1327
            case T_ABSTRACT:
1328
                $isAbstract = true;
1329
                break;
1330
            case T_FINAL:
1331
                $isFinal = true;
1332
                break;
1333
            case T_STATIC:
1334
                $isStatic = true;
1335
                break;
1336
            }//end switch
1337
        }//end for
1338
1339
        return array(
1340
                'scope'           => $scope,
1341
                'scope_specified' => $scopeSpecified,
1342
                'is_abstract'     => $isAbstract,
1343
                'is_final'        => $isFinal,
1344
                'is_static'       => $isStatic,
1345
                'is_closure'      => $isClosure,
1346
               );
1347
1348
    }//end getMethodProperties()
1349
1350
1351
    /**
1352
     * Returns the visibility and implementation properties of the class member
1353
     * variable found at the specified position in the stack.
1354
     *
1355
     * The format of the array is:
1356
     *
1357
     * <code>
1358
     *   array(
1359
     *    'scope'       => 'public', // public protected or protected
1360
     *    'is_static'   => false,    // true if the static keyword was found.
1361
     *   );
1362
     * </code>
1363
     *
1364
     * @param int $stackPtr The position in the stack of the T_VARIABLE token to
1365
     *                      acquire the properties for.
1366
     *
1367
     * @return array
1368
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified position is not a
1369
     *                                   T_VARIABLE token, or if the position is not
1370
     *                                   a class member variable.
1371
     */
1372
    public function getMemberProperties($stackPtr)
1373
    {
1374
        if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) {
1375
            throw new TokenizerException('$stackPtr must be of type T_VARIABLE');
1376
        }
1377
1378
        $conditions = array_keys($this->tokens[$stackPtr]['conditions']);
1379
        $ptr        = array_pop($conditions);
1380
        if (isset($this->tokens[$ptr]) === false
1381
            || ($this->tokens[$ptr]['code'] !== T_CLASS
1382
            && $this->tokens[$ptr]['code'] !== T_TRAIT)
1383
        ) {
1384
            if (isset($this->tokens[$ptr]) === true
1385
                && $this->tokens[$ptr]['code'] === T_INTERFACE
1386
            ) {
1387
                // T_VARIABLEs in interfaces can actually be method arguments
1388
                // but they wont be seen as being inside the method because there
1389
                // are no scope openers and closers for abstract methods. If it is in
1390
                // parentheses, we can be pretty sure it is a method argument.
1391
                if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
1392
                    || empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
1393
                ) {
1394
                    $error = 'Possible parse error: interfaces may not include member vars';
1395
                    $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
1396
                    return array();
1397
                }
1398
            } else {
1399
                throw new TokenizerException('$stackPtr is not a class member var');
1400
            }
1401
        }
1402
1403
        $valid = array(
1404
                  T_PUBLIC      => T_PUBLIC,
1405
                  T_PRIVATE     => T_PRIVATE,
1406
                  T_PROTECTED   => T_PROTECTED,
1407
                  T_STATIC      => T_STATIC,
1408
                  T_WHITESPACE  => T_WHITESPACE,
1409
                  T_COMMENT     => T_COMMENT,
1410
                  T_DOC_COMMENT => T_DOC_COMMENT,
1411
                  T_VARIABLE    => T_VARIABLE,
1412
                  T_COMMA       => T_COMMA,
1413
                 );
1414
1415
        $scope          = 'public';
1416
        $scopeSpecified = false;
1417
        $isStatic       = false;
1418
1419
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1420
            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1421
                break;
1422
            }
1423
1424
            switch ($this->tokens[$i]['code']) {
1425
            case T_PUBLIC:
1426
                $scope          = 'public';
1427
                $scopeSpecified = true;
1428
                break;
1429
            case T_PRIVATE:
1430
                $scope          = 'private';
1431
                $scopeSpecified = true;
1432
                break;
1433
            case T_PROTECTED:
1434
                $scope          = 'protected';
1435
                $scopeSpecified = true;
1436
                break;
1437
            case T_STATIC:
1438
                $isStatic = true;
1439
                break;
1440
            }
1441
        }//end for
1442
1443
        return array(
1444
                'scope'           => $scope,
1445
                'scope_specified' => $scopeSpecified,
1446
                'is_static'       => $isStatic,
1447
               );
1448
1449
    }//end getMemberProperties()
1450
1451
1452
    /**
1453
     * Returns the visibility and implementation properties of a class.
1454
     *
1455
     * The format of the array is:
1456
     * <code>
1457
     *   array(
1458
     *    'is_abstract' => false, // true if the abstract keyword was found.
1459
     *    'is_final'    => false, // true if the final keyword was found.
1460
     *   );
1461
     * </code>
1462
     *
1463
     * @param int $stackPtr The position in the stack of the T_CLASS token to
1464
     *                      acquire the properties for.
1465
     *
1466
     * @return array
1467
     * @throws Symplify\PHP7_CodeSniffer_Exception If the specified position is not a
1468
     *                                   T_CLASS token.
1469
     */
1470
    public function getClassProperties($stackPtr)
1471
    {
1472
        if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
1473
            throw new TokenizerException('$stackPtr must be of type T_CLASS');
1474
        }
1475
1476
        $valid = array(
1477
                  T_FINAL       => T_FINAL,
1478
                  T_ABSTRACT    => T_ABSTRACT,
1479
                  T_WHITESPACE  => T_WHITESPACE,
1480
                  T_COMMENT     => T_COMMENT,
1481
                  T_DOC_COMMENT => T_DOC_COMMENT,
1482
                 );
1483
1484
        $isAbstract = false;
1485
        $isFinal    = false;
1486
1487
        for ($i = ($stackPtr - 1); $i > 0; $i--) {
1488
            if (isset($valid[$this->tokens[$i]['code']]) === false) {
1489
                break;
1490
            }
1491
1492
            switch ($this->tokens[$i]['code']) {
1493
            case T_ABSTRACT:
1494
                $isAbstract = true;
1495
                break;
1496
1497
            case T_FINAL:
1498
                $isFinal = true;
1499
                break;
1500
            }
1501
        }//end for
1502
1503
        return array(
1504
                'is_abstract' => $isAbstract,
1505
                'is_final'    => $isFinal,
1506
               );
1507
1508
    }//end getClassProperties()
1509
1510
1511
    /**
1512
     * Determine if the passed token is a reference operator.
1513
     *
1514
     * Returns true if the specified token position represents a reference.
1515
     * Returns false if the token represents a bitwise operator.
1516
     *
1517
     * @param int $stackPtr The position of the T_BITWISE_AND token.
1518
     *
1519
     * @return boolean
1520
     */
1521
    public function isReference($stackPtr)
1522
    {
1523
        if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
1524
            return false;
1525
        }
1526
1527
        $tokenBefore = $this->findPrevious(
1528
            Util\Tokens::$emptyTokens,
1529
            ($stackPtr - 1),
1530
            null,
1531
            true
1532
        );
1533
1534
        if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) {
1535
            // Function returns a reference.
1536
            return true;
1537
        }
1538
1539
        if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
1540
            // Inside a foreach loop, this is a reference.
1541
            return true;
1542
        }
1543
1544
        if ($this->tokens[$tokenBefore]['code'] === T_AS) {
1545
            // Inside a foreach loop, this is a reference.
1546
            return true;
1547
        }
1548
1549
        if ($this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY) {
1550
            // Inside an array declaration, this is a reference.
1551
            return true;
1552
        }
1553
1554
        if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) {
1555
            // This is directly after an assignment. It's a reference. Even if
1556
            // it is part of an operation, the other tests will handle it.
1557
            return true;
1558
        }
1559
1560
        if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) {
1561
            $brackets    = $this->tokens[$stackPtr]['nested_parenthesis'];
1562
            $lastBracket = array_pop($brackets);
1563
            if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) {
1564
                $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']];
1565
                if ($owner['code'] === T_FUNCTION
1566
                    || $owner['code'] === T_CLOSURE
1567
                    || $owner['code'] === T_ARRAY
1568
                ) {
1569
                    // Inside a function or array declaration, this is a reference.
1570
                    return true;
1571
                }
1572
            } else {
1573
                $prev = false;
1574
                for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
1575
                    if ($this->tokens[$t]['code'] !== T_WHITESPACE) {
1576
                        $prev = $t;
1577
                        break;
1578
                    }
1579
                }
1580
1581
                if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) {
1582
                    return true;
1583
                }
1584
            }//end if
1585
        }//end if
1586
1587
        $tokenAfter = $this->findNext(
1588
            Util\Tokens::$emptyTokens,
1589
            ($stackPtr + 1),
1590
            null,
1591
            true
1592
        );
1593
1594
        if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE
1595
            && ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
1596
            || $this->tokens[$tokenBefore]['code'] === T_COMMA)
1597
        ) {
1598
            return true;
1599
        }
1600
1601
        return false;
1602
1603
    }//end isReference()
1604
1605
1606
    /**
1607
     * Returns the content of the tokens from the specified start position in
1608
     * the token stack for the specified length.
1609
     *
1610
     * @param int $start  The position to start from in the token stack.
1611
     * @param int $length The length of tokens to traverse from the start pos.
1612
     *
1613
     * @return string The token contents.
1614
     */
1615
    public function getTokensAsString($start, $length)
1616
    {
1617
        $str = '';
1618
        $end = ($start + $length);
1619
        if ($end > $this->numTokens) {
1620
            $end = $this->numTokens;
1621
        }
1622
1623
        for ($i = $start; $i < $end; $i++) {
1624
            $str .= $this->tokens[$i]['content'];
1625
        }
1626
1627
        return $str;
1628
1629
    }//end getTokensAsString()
1630
1631
1632
    /**
1633
     * Returns the position of the previous specified token(s).
1634
     *
1635
     * If a value is specified, the previous token of the specified type(s)
1636
     * containing the specified value will be returned.
1637
     *
1638
     * Returns false if no token can be found.
1639
     *
1640
     * @param int|array $types   The type(s) of tokens to search for.
1641
     * @param int       $start   The position to start searching from in the
1642
     *                           token stack.
1643
     * @param int       $end     The end position to fail if no token is found.
1644
     *                           if not specified or null, end will default to
1645
     *                           the start of the token stack.
1646
     * @param bool      $exclude If true, find the previous token that is NOT of
1647
     *                           the types specified in $types.
1648
     * @param string    $value   The value that the token(s) must be equal to.
1649
     *                           If value is omitted, tokens with any value will
1650
     *                           be returned.
1651
     * @param bool      $local   If true, tokens outside the current statement
1652
     *                           will not be checked. IE. checking will stop
1653
     *                           at the previous semi-colon found.
1654
     *
1655
     * @return int|bool
1656
     * @see    findNext()
1657
     */
1658
    public function findPrevious(
1659
        $types,
1660
        $start,
1661
        $end=null,
1662
        $exclude=false,
1663
        $value=null,
1664
        $local=false
1665
    ) {
1666
        $types = (array) $types;
1667
1668
        if ($end === null) {
1669
            $end = 0;
1670
        }
1671
1672
        for ($i = $start; $i >= $end; $i--) {
1673
            $found = (bool) $exclude;
1674 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...
1675
                if ($this->tokens[$i]['code'] === $type) {
1676
                    $found = !$exclude;
1677
                    break;
1678
                }
1679
            }
1680
1681 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...
1682
                if ($value === null) {
1683
                    return $i;
1684
                } else if ($this->tokens[$i]['content'] === $value) {
1685
                    return $i;
1686
                }
1687
            }
1688
1689 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...
1690
                if (isset($this->tokens[$i]['scope_opener']) === true
1691
                    && $i === $this->tokens[$i]['scope_closer']
1692
                ) {
1693
                    $i = $this->tokens[$i]['scope_opener'];
1694
                } else if (isset($this->tokens[$i]['bracket_opener']) === true
1695
                    && $i === $this->tokens[$i]['bracket_closer']
1696
                ) {
1697
                    $i = $this->tokens[$i]['bracket_opener'];
1698
                } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
1699
                    && $i === $this->tokens[$i]['parenthesis_closer']
1700
                ) {
1701
                    $i = $this->tokens[$i]['parenthesis_opener'];
1702
                } else if ($this->tokens[$i]['code'] === T_SEMICOLON) {
1703
                    break;
1704
                }
1705
            }
1706
        }//end for
1707
1708
        return false;
1709
1710
    }//end findPrevious()
1711
1712
1713
    /**
1714
     * Returns the position of the next specified token(s).
1715
     *
1716
     * If a value is specified, the next token of the specified type(s)
1717
     * containing the specified value will be returned.
1718
     *
1719
     * Returns false if no token can be found.
1720
     *
1721
     * @param int|array $types   The type(s) of tokens to search for.
1722
     * @param int       $start   The position to start searching from in the
1723
     *                           token stack.
1724
     * @param int       $end     The end position to fail if no token is found.
1725
     *                           if not specified or null, end will default to
1726
     *                           the end of the token stack.
1727
     * @param bool      $exclude If true, find the next token that is NOT of
1728
     *                           a type specified in $types.
1729
     * @param string    $value   The value that the token(s) must be equal to.
1730
     *                           If value is omitted, tokens with any value will
1731
     *                           be returned.
1732
     * @param bool      $local   If true, tokens outside the current statement
1733
     *                           will not be checked. i.e., checking will stop
1734
     *                           at the next semi-colon found.
1735
     *
1736
     * @return int|bool
1737
     * @see    findPrevious()
1738
     */
1739
    public function findNext(
1740
        $types,
1741
        $start,
1742
        $end=null,
1743
        $exclude=false,
1744
        $value=null,
1745
        $local=false
1746
    ) {
1747
        $types = (array) $types;
1748
1749
        if ($end === null || $end > $this->numTokens) {
1750
            $end = $this->numTokens;
1751
        }
1752
1753
        for ($i = $start; $i < $end; $i++) {
1754
            $found = (bool) $exclude;
1755 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...
1756
                if ($this->tokens[$i]['code'] === $type) {
1757
                    $found = !$exclude;
1758
                    break;
1759
                }
1760
            }
1761
1762 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...
1763
                if ($value === null) {
1764
                    return $i;
1765
                } else if ($this->tokens[$i]['content'] === $value) {
1766
                    return $i;
1767
                }
1768
            }
1769
1770
            if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
1771
                break;
1772
            }
1773
        }//end for
1774
1775
        return false;
1776
1777
    }//end findNext()
1778
1779
1780
    /**
1781
     * Returns the position of the first non-whitespace token in a statement.
1782
     *
1783
     * @param int       $start  The position to start searching from in the token stack.
1784
     * @param int|array $ignore Token types that should not be considered stop points.
1785
     *
1786
     * @return int
1787
     */
1788
    public function findStartOfStatement($start, $ignore=null)
1789
    {
1790
        $endTokens = Util\Tokens::$blockOpeners;
1791
1792
        $endTokens[T_COLON]            = true;
1793
        $endTokens[T_COMMA]            = true;
1794
        $endTokens[T_DOUBLE_ARROW]     = true;
1795
        $endTokens[T_SEMICOLON]        = true;
1796
        $endTokens[T_OPEN_TAG]         = true;
1797
        $endTokens[T_CLOSE_TAG]        = true;
1798
        $endTokens[T_OPEN_SHORT_ARRAY] = true;
1799
1800 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...
1801
            $ignore = (array) $ignore;
1802
            foreach ($ignore as $code) {
1803
                if (isset($endTokens[$code]) === true) {
1804
                    unset($endTokens[$code]);
1805
                }
1806
            }
1807
        }
1808
1809
        $lastNotEmpty = $start;
1810
1811
        for ($i = $start; $i >= 0; $i--) {
1812
            if (isset($endTokens[$this->tokens[$i]['code']]) === true) {
1813
                // Found the end of the previous statement.
1814
                return $lastNotEmpty;
1815
            }
1816
1817 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...
1818
                && $i === $this->tokens[$i]['scope_closer']
1819
            ) {
1820
                // Found the end of the previous scope block.
1821
                return $lastNotEmpty;
1822
            }
1823
1824
            // Skip nested statements.
1825
            if (isset($this->tokens[$i]['bracket_opener']) === true
1826
                && $i === $this->tokens[$i]['bracket_closer']
1827
            ) {
1828
                $i = $this->tokens[$i]['bracket_opener'];
1829
            } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
1830
                && $i === $this->tokens[$i]['parenthesis_closer']
1831
            ) {
1832
                $i = $this->tokens[$i]['parenthesis_opener'];
1833
            }
1834
1835 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...
1836
                $lastNotEmpty = $i;
1837
            }
1838
        }//end for
1839
1840
        return 0;
1841
1842
    }//end findStartOfStatement()
1843
1844
1845
    /**
1846
     * Returns the position of the last non-whitespace token in a statement.
1847
     *
1848
     * @param int       $start  The position to start searching from in the token stack.
1849
     * @param int|array $ignore Token types that should not be considered stop points.
1850
     *
1851
     * @return int
1852
     */
1853
    public function findEndOfStatement($start, $ignore=null)
1854
    {
1855
        $endTokens = array(
1856
                      T_COLON                => true,
1857
                      T_COMMA                => true,
1858
                      T_DOUBLE_ARROW         => true,
1859
                      T_SEMICOLON            => true,
1860
                      T_CLOSE_PARENTHESIS    => true,
1861
                      T_CLOSE_SQUARE_BRACKET => true,
1862
                      T_CLOSE_CURLY_BRACKET  => true,
1863
                      T_CLOSE_SHORT_ARRAY    => true,
1864
                      T_OPEN_TAG             => true,
1865
                      T_CLOSE_TAG            => true,
1866
                     );
1867
1868 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...
1869
            $ignore = (array) $ignore;
1870
            foreach ($ignore as $code) {
1871
                if (isset($endTokens[$code]) === true) {
1872
                    unset($endTokens[$code]);
1873
                }
1874
            }
1875
        }
1876
1877
        $lastNotEmpty = $start;
1878
1879
        for ($i = $start; $i < $this->numTokens; $i++) {
1880
            if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) {
1881
                // Found the end of the statement.
1882
                if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
1883
                    || $this->tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
1884
                    || $this->tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
1885
                    || $this->tokens[$i]['code'] === T_OPEN_TAG
1886
                    || $this->tokens[$i]['code'] === T_CLOSE_TAG
1887
                ) {
1888
                    return $lastNotEmpty;
1889
                }
1890
1891
                return $i;
1892
            }
1893
1894
            // Skip nested statements.
1895 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...
1896
                && ($i === $this->tokens[$i]['scope_opener']
1897
                || $i === $this->tokens[$i]['scope_condition'])
1898
            ) {
1899
                $i = $this->tokens[$i]['scope_closer'];
1900
            } else if (isset($this->tokens[$i]['bracket_closer']) === true
1901
                && $i === $this->tokens[$i]['bracket_opener']
1902
            ) {
1903
                $i = $this->tokens[$i]['bracket_closer'];
1904
            } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
1905
                && $i === $this->tokens[$i]['parenthesis_opener']
1906
            ) {
1907
                $i = $this->tokens[$i]['parenthesis_closer'];
1908
            }
1909
1910 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...
1911
                $lastNotEmpty = $i;
1912
            }
1913
        }//end for
1914
1915
        return ($this->numTokens - 1);
1916
1917
    }//end findEndOfStatement()
1918
1919
1920
    /**
1921
     * Returns the position of the first token on a line, matching given type.
1922
     *
1923
     * Returns false if no token can be found.
1924
     *
1925
     * @param int|array $types   The type(s) of tokens to search for.
1926
     * @param int       $start   The position to start searching from in the
1927
     *                           token stack. The first token matching on
1928
     *                           this line before this token will be returned.
1929
     * @param bool      $exclude If true, find the token that is NOT of
1930
     *                           the types specified in $types.
1931
     * @param string    $value   The value that the token must be equal to.
1932
     *                           If value is omitted, tokens with any value will
1933
     *                           be returned.
1934
     *
1935
     * @return int | bool
1936
     */
1937
    public function findFirstOnLine($types, $start, $exclude=false, $value=null)
1938
    {
1939
        if (is_array($types) === false) {
1940
            $types = array($types);
1941
        }
1942
1943
        $foundToken = false;
1944
1945
        for ($i = $start; $i >= 0; $i--) {
1946
            if ($this->tokens[$i]['line'] < $this->tokens[$start]['line']) {
1947
                break;
1948
            }
1949
1950
            $found = $exclude;
1951
            foreach ($types as $type) {
1952
                if ($exclude === false) {
1953
                    if ($this->tokens[$i]['code'] === $type) {
1954
                        $found = true;
1955
                        break;
1956
                    }
1957
                } else {
1958
                    if ($this->tokens[$i]['code'] === $type) {
1959
                        $found = false;
1960
                        break;
1961
                    }
1962
                }
1963
            }
1964
1965
            if ($found === true) {
1966
                if ($value === null) {
1967
                    $foundToken = $i;
1968
                } else if ($this->tokens[$i]['content'] === $value) {
1969
                    $foundToken = $i;
1970
                }
1971
            }
1972
        }//end for
1973
1974
        return $foundToken;
1975
1976
    }//end findFirstOnLine()
1977
1978
1979
    /**
1980
     * Determine if the passed token has a condition of one of the passed types.
1981
     *
1982
     * @param int       $stackPtr The position of the token we are checking.
1983
     * @param int|array $types    The type(s) of tokens to search for.
1984
     *
1985
     * @return boolean
1986
     */
1987
    public function hasCondition($stackPtr, $types)
1988
    {
1989
        // Check for the existence of the token.
1990
        if (isset($this->tokens[$stackPtr]) === false) {
1991
            return false;
1992
        }
1993
1994
        // Make sure the token has conditions.
1995
        if (isset($this->tokens[$stackPtr]['conditions']) === false) {
1996
            return false;
1997
        }
1998
1999
        $types      = (array) $types;
2000
        $conditions = $this->tokens[$stackPtr]['conditions'];
2001
2002
        foreach ($types as $type) {
2003
            if (in_array($type, $conditions) === true) {
2004
                // We found a token with the required type.
2005
                return true;
2006
            }
2007
        }
2008
2009
        return false;
2010
2011
    }//end hasCondition()
2012
2013
2014
    /**
2015
     * Return the position of the condition for the passed token.
2016
     *
2017
     * Returns FALSE if the token does not have the condition.
2018
     *
2019
     * @param int $stackPtr The position of the token we are checking.
2020
     * @param int $type     The type of token to search for.
2021
     *
2022
     * @return int
2023
     */
2024
    public function getCondition($stackPtr, $type)
2025
    {
2026
        // Check for the existence of the token.
2027
        if (isset($this->tokens[$stackPtr]) === false) {
2028
            return false;
2029
        }
2030
2031
        // Make sure the token has conditions.
2032
        if (isset($this->tokens[$stackPtr]['conditions']) === false) {
2033
            return false;
2034
        }
2035
2036
        $conditions = $this->tokens[$stackPtr]['conditions'];
2037
        foreach ($conditions as $token => $condition) {
2038
            if ($condition === $type) {
2039
                return $token;
2040
            }
2041
        }
2042
2043
        return false;
2044
2045
    }//end getCondition()
2046
2047
2048
    /**
2049
     * Returns the name of the class that the specified class extends.
2050
     *
2051
     * Returns FALSE on error or if there is no extended class name.
2052
     *
2053
     * @param int $stackPtr The stack position of the class.
2054
     *
2055
     * @return string
2056
     */
2057
    public function findExtendedClassName($stackPtr)
2058
    {
2059
        // Check for the existence of the token.
2060
        if (isset($this->tokens[$stackPtr]) === false) {
2061
            return false;
2062
        }
2063
2064
        if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
2065
            return false;
2066
        }
2067
2068
        if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
2069
            return false;
2070
        }
2071
2072
        $classCloserIndex = $this->tokens[$stackPtr]['scope_closer'];
2073
        $extendsIndex     = $this->findNext(T_EXTENDS, $stackPtr, $classCloserIndex);
2074
        if (false === $extendsIndex) {
2075
            return false;
2076
        }
2077
2078
        $find = array(
2079
                 T_NS_SEPARATOR,
2080
                 T_STRING,
2081
                 T_WHITESPACE,
2082
                );
2083
2084
        $end  = $this->findNext($find, ($extendsIndex + 1), $classCloserIndex, true);
2085
        $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
2086
        $name = trim($name);
2087
2088
        if ($name === '') {
2089
            return false;
2090
        }
2091
2092
        return $name;
2093
2094
    }//end findExtendedClassName()
2095
2096
2097
}//end class
2098