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

AbstractPatternSniff::parse()   D

Complexity

Conditions 15
Paths 131

Size

Total Lines 79
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 79
rs 4.7516
cc 15
eloc 43
nc 131
nop 1

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Processes pattern strings and checks that the code conforms to the pattern.
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\Sniffs;
11
12
use Symplify\PHP7_CodeSniffer\Files\File;
13
use Symplify\PHP7_CodeSniffer\Config;
14
use Symplify\PHP7_CodeSniffer\Util\Tokens;
15
use Symplify\PHP7_CodeSniffer\Tokenizers\PHP;
16
use Symplify\PHP7_CodeSniffer\Exceptions\RuntimeException;
17
18
abstract class AbstractPatternSniff implements Sniff
19
{
20
21
    /**
22
     * If true, comments will be ignored if they are found in the code.
23
     *
24
     * @var boolean
25
     */
26
    public $ignoreComments = false;
27
28
    /**
29
     * The current file being checked.
30
     *
31
     * @var string
32
     */
33
    protected $currFile = '';
34
35
    /**
36
     * The parsed patterns array.
37
     *
38
     * @var array
39
     */
40
    private $_parsedPatterns = array();
41
42
    /**
43
     * Tokens that this sniff wishes to process outside of the patterns.
44
     *
45
     * @var int[]
46
     * @see registerSupplementary()
47
     * @see processSupplementary()
48
     */
49
    private $_supplementaryTokens = array();
50
51
    /**
52
     * Positions in the stack where errors have occurred.
53
     *
54
     * @var array<int, bool>
55
     */
56
    private $_errorPos = array();
57
58
59
    /**
60
     * Constructs a Symplify\PHP7_CodeSniffer_Standards_AbstractPatternSniff.
61
     *
62
     * @param boolean $ignoreComments If true, comments will be ignored.
63
     */
64
    public function __construct($ignoreComments=null)
65
    {
66
        // This is here for backwards compatibility.
67
        if ($ignoreComments !== null) {
68
            $this->ignoreComments = $ignoreComments;
69
        }
70
71
        $this->_supplementaryTokens = $this->registerSupplementary();
72
73
    }//end __construct()
74
75
76
    /**
77
     * Registers the tokens to listen to.
78
     *
79
     * Classes extending <i>AbstractPatternTest</i> should implement the
80
     * <i>getPatterns()</i> method to register the patterns they wish to test.
81
     *
82
     * @return int[]
83
     * @see    process()
84
     */
85
    final public function register()
86
    {
87
        $listenTypes = array();
88
        $patterns    = $this->getPatterns();
89
90
        foreach ($patterns as $pattern) {
91
            $parsedPattern = $this->parse($pattern);
92
93
            // Find a token position in the pattern that we can use
94
            // for a listener token.
95
            $pos           = $this->getListenerTokenPos($parsedPattern);
96
            $tokenType     = $parsedPattern[$pos]['token'];
97
            $listenTypes[] = $tokenType;
98
99
            $patternArray = array(
100
                             'listen_pos'   => $pos,
101
                             'pattern'      => $parsedPattern,
102
                             'pattern_code' => $pattern,
103
                            );
104
105
            if (isset($this->_parsedPatterns[$tokenType]) === false) {
106
                $this->_parsedPatterns[$tokenType] = array();
107
            }
108
109
            $this->_parsedPatterns[$tokenType][] = $patternArray;
110
        }//end foreach
111
112
        return array_unique(array_merge($listenTypes, $this->_supplementaryTokens));
113
114
    }//end register()
115
116
117
    /**
118
     * Returns the token types that the specified pattern is checking for.
119
     *
120
     * Returned array is in the format:
121
     * <code>
122
     *   array(
123
     *      T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
124
     *                         // should occur in the pattern.
125
     *   );
126
     * </code>
127
     *
128
     * @param array $pattern The parsed pattern to find the acquire the token
129
     *                       types from.
130
     *
131
     * @return array<int, int>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
132
     */
133
    private function getPatternTokenTypes($pattern)
134
    {
135
        $tokenTypes = array();
136
        foreach ($pattern as $pos => $patternInfo) {
137
            if ($patternInfo['type'] === 'token') {
138
                if (isset($tokenTypes[$patternInfo['token']]) === false) {
139
                    $tokenTypes[$patternInfo['token']] = $pos;
140
                }
141
            }
142
        }
143
144
        return $tokenTypes;
145
146
    }//end getPatternTokenTypes()
147
148
149
    /**
150
     * Returns the position in the pattern that this test should register as
151
     * a listener for the pattern.
152
     *
153
     * @param array $pattern The pattern to acquire the listener for.
154
     *
155
     * @return int The position in the pattern that this test should register
156
     *             as the listener.
157
     * @throws Symplify\PHP7_CodeSniffer_Exception If we could not determine a token
158
     *                                         to listen for.
159
     */
160
    private function getListenerTokenPos($pattern)
161
    {
162
        $tokenTypes = $this->getPatternTokenTypes($pattern);
163
        $tokenCodes = array_keys($tokenTypes);
164
        $token      = Tokens::getHighestWeightedToken($tokenCodes);
165
166
        // If we could not get a token.
167
        if ($token === false) {
168
            $error = 'Could not determine a token to listen for';
169
            throw new RuntimeException($error);
170
        }
171
172
        return $tokenTypes[$token];
173
174
    }//end getListenerTokenPos()
175
176
177
    /**
178
     * Processes the test.
179
     *
180
     * @param Symplify\PHP7_CodeSniffer_File $phpcsFile The Symplify\PHP7_CodeSniffer file where the
181
     *                                        token occurred.
182
     * @param int                  $stackPtr  The position in the tokens stack
183
     *                                        where the listening token type was
184
     *                                        found.
185
     *
186
     * @return void
187
     * @see    register()
188
     */
189
    final public function process(File $phpcsFile, $stackPtr)
190
    {
191
        $file = $phpcsFile->getFilename();
192
        if ($this->currFile !== $file) {
193
            // We have changed files, so clean up.
194
            $this->_errorPos = array();
195
            $this->currFile  = $file;
196
        }
197
198
        $tokens = $phpcsFile->getTokens();
199
200
        if (in_array($tokens[$stackPtr]['code'], $this->_supplementaryTokens) === true) {
201
            $this->processSupplementary($phpcsFile, $stackPtr);
202
        }
203
204
        $type = $tokens[$stackPtr]['code'];
205
206
        // If the type is not set, then it must have been a token registered
207
        // with registerSupplementary().
208
        if (isset($this->_parsedPatterns[$type]) === false) {
209
            return;
210
        }
211
212
        $allErrors = array();
213
214
        // Loop over each pattern that is listening to the current token type
215
        // that we are processing.
216
        foreach ($this->_parsedPatterns[$type] as $patternInfo) {
217
            // If processPattern returns false, then the pattern that we are
218
            // checking the code with must not be designed to check that code.
219
            $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr);
220
            if ($errors === false) {
221
                // The pattern didn't match.
222
                continue;
223
            } else if (empty($errors) === true) {
224
                // The pattern matched, but there were no errors.
225
                break;
226
            }
227
228
            foreach ($errors as $stackPtr => $error) {
229
                if (isset($this->_errorPos[$stackPtr]) === false) {
230
                    $this->_errorPos[$stackPtr] = true;
231
                    $allErrors[$stackPtr]       = $error;
232
                }
233
            }
234
        }
235
236
        foreach ($allErrors as $stackPtr => $error) {
237
            $phpcsFile->addError($error, $stackPtr, 'Found');
238
        }
239
240
    }//end process()
241
242
243
    /**
244
     * Processes the pattern and verifies the code at $stackPtr.
245
     *
246
     * @param array                $patternInfo Information about the pattern used
247
     *                                          for checking, which includes are
248
     *                                          parsed token representation of the
249
     *                                          pattern.
250
     * @param Symplify\PHP7_CodeSniffer_File $phpcsFile   The Symplify\PHP7_CodeSniffer file where the
251
     *                                          token occurred.
252
     * @param int                  $stackPtr    The position in the tokens stack where
253
     *                                          the listening token type was found.
254
     *
255
     * @return array
256
     */
257
    protected function processPattern($patternInfo, File $phpcsFile, $stackPtr)
258
    {
259
        $tokens      = $phpcsFile->getTokens();
260
        $pattern     = $patternInfo['pattern'];
261
        $patternCode = $patternInfo['pattern_code'];
262
        $errors      = array();
263
        $found       = '';
264
265
        $ignoreTokens = array(T_WHITESPACE);
266
        if ($this->ignoreComments === true) {
267
            $ignoreTokens
268
                = array_merge($ignoreTokens, Tokens::$commentTokens);
269
        }
270
271
        $origStackPtr = $stackPtr;
272
        $hasError     = false;
273
274
        if ($patternInfo['listen_pos'] > 0) {
275
            $stackPtr--;
276
277
            for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
278
                if ($pattern[$i]['type'] === 'token') {
279
                    if ($pattern[$i]['token'] === T_WHITESPACE) {
280
                        if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
281
                            $found = $tokens[$stackPtr]['content'].$found;
282
                        }
283
284
                        // Only check the size of the whitespace if this is not
285
                        // the first token. We don't care about the size of
286
                        // leading whitespace, just that there is some.
287
                        if ($i !== 0) {
288
                            if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
289
                                $hasError = true;
290
                            }
291
                        }
292
                    } else {
293
                        // Check to see if this important token is the same as the
294
                        // previous important token in the pattern. If it is not,
295
                        // then the pattern cannot be for this piece of code.
296
                        $prev = $phpcsFile->findPrevious(
297
                            $ignoreTokens,
298
                            $stackPtr,
299
                            null,
300
                            true
301
                        );
302
303 View Code Duplication
                        if ($prev === 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...
304
                            || $tokens[$prev]['code'] !== $pattern[$i]['token']
305
                        ) {
306
                            return false;
307
                        }
308
309
                        // If we skipped past some whitespace tokens, then add them
310
                        // to the found string.
311
                        $tokenContent = $phpcsFile->getTokensAsString(
312
                            ($prev + 1),
313
                            ($stackPtr - $prev - 1)
314
                        );
315
316
                        $found = $tokens[$prev]['content'].$tokenContent.$found;
317
318
                        if (isset($pattern[($i - 1)]) === true
319
                            && $pattern[($i - 1)]['type'] === 'skip'
320
                        ) {
321
                            $stackPtr = $prev;
322
                        } else {
323
                            $stackPtr = ($prev - 1);
324
                        }
325
                    }//end if
326
                } else if ($pattern[$i]['type'] === 'skip') {
327
                    // Skip to next piece of relevant code.
328
                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
329
                        $to = 'parenthesis_opener';
330
                    } else {
331
                        $to = 'scope_opener';
332
                    }
333
334
                    // Find the previous opener.
335
                    $next = $phpcsFile->findPrevious(
336
                        $ignoreTokens,
337
                        $stackPtr,
338
                        null,
339
                        true
340
                    );
341
342
                    if ($next === false || isset($tokens[$next][$to]) === false) {
343
                        // If there was not opener, then we must be
344
                        // using the wrong pattern.
345
                        return false;
346
                    }
347
348
                    if ($to === 'parenthesis_opener') {
349
                        $found = '{'.$found;
350
                    } else {
351
                        $found = '('.$found;
352
                    }
353
354
                    $found = '...'.$found;
355
356
                    // Skip to the opening token.
357
                    $stackPtr = ($tokens[$next][$to] - 1);
358
                } else if ($pattern[$i]['type'] === 'string') {
359
                    $found = 'abc';
360
                } else if ($pattern[$i]['type'] === 'newline') {
361
                    if ($this->ignoreComments === true
362
                        && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
363
                    ) {
364
                        $startComment = $phpcsFile->findPrevious(
365
                            Tokens::$commentTokens,
366
                            ($stackPtr - 1),
367
                            null,
368
                            true
369
                        );
370
371
                        if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
372
                            $startComment++;
373
                        }
374
375
                        $tokenContent = $phpcsFile->getTokensAsString(
376
                            $startComment,
377
                            ($stackPtr - $startComment + 1)
378
                        );
379
380
                        $found    = $tokenContent.$found;
381
                        $stackPtr = ($startComment - 1);
382
                    }
383
384
                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
385
                        if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
386
                            $found = $tokens[$stackPtr]['content'].$found;
387
388
                            // This may just be an indent that comes after a newline
389
                            // so check the token before to make sure. If it is a newline, we
390
                            // can ignore the error here.
391
                            if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
392
                                && ($this->ignoreComments === true
393
                                && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
394
                            ) {
395
                                $hasError = true;
396
                            } else {
397
                                $stackPtr--;
398
                            }
399
                        } else {
400
                            $found = 'EOL'.$found;
401
                        }
402
                    } else {
403
                        $found    = $tokens[$stackPtr]['content'].$found;
404
                        $hasError = true;
405
                    }//end if
406
407
                    if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
408
                        // Make sure they only have 1 newline.
409
                        $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
410 View Code Duplication
                        if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
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...
411
                            $hasError = true;
412
                        }
413
                    }
414
                }//end if
415
            }//end for
416
        }//end if
417
418
        $stackPtr          = $origStackPtr;
419
        $lastAddedStackPtr = null;
420
        $patternLen        = count($pattern);
421
422
        for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
423
            if (isset($tokens[$stackPtr]) === false) {
424
                break;
425
            }
426
427
            if ($pattern[$i]['type'] === 'token') {
428
                if ($pattern[$i]['token'] === T_WHITESPACE) {
429
                    if ($this->ignoreComments === true) {
430
                        // If we are ignoring comments, check to see if this current
431
                        // token is a comment. If so skip it.
432
                        if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
433
                            continue;
434
                        }
435
436
                        // If the next token is a comment, the we need to skip the
437
                        // current token as we should allow a space before a
438
                        // comment for readability.
439
                        if (isset($tokens[($stackPtr + 1)]) === true
440
                            && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
441
                        ) {
442
                            continue;
443
                        }
444
                    }
445
446
                    $tokenContent = '';
447
                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
448
                        if (isset($pattern[($i + 1)]) === false) {
449
                            // This is the last token in the pattern, so just compare
450
                            // the next token of content.
451
                            $tokenContent = $tokens[$stackPtr]['content'];
452
                        } else {
453
                            // Get all the whitespace to the next token.
454
                            $next = $phpcsFile->findNext(
455
                                Tokens::$emptyTokens,
456
                                $stackPtr,
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffer\Files\File::findNext() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
457
                                null,
458
                                true
459
                            );
460
461
                            $tokenContent = $phpcsFile->getTokensAsString(
462
                                $stackPtr,
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffe...le::getTokensAsString() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
463
                                ($next - $stackPtr)
464
                            );
465
466
                            $lastAddedStackPtr = $stackPtr;
467
                            $stackPtr          = $next;
468
                        }//end if
469
470
                        if ($stackPtr !== $lastAddedStackPtr) {
471
                            $found .= $tokenContent;
472
                        }
473
                    } else {
474
                        if ($stackPtr !== $lastAddedStackPtr) {
475
                            $found            .= $tokens[$stackPtr]['content'];
476
                            $lastAddedStackPtr = $stackPtr;
477
                        }
478
                    }//end if
479
480
                    if (isset($pattern[($i + 1)]) === true
481
                        && $pattern[($i + 1)]['type'] === 'skip'
482
                    ) {
483
                        // The next token is a skip token, so we just need to make
484
                        // sure the whitespace we found has *at least* the
485
                        // whitespace required.
486
                        if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
487
                            $hasError = true;
488
                        }
489
                    } else {
490
                        if ($tokenContent !== $pattern[$i]['value']) {
491
                            $hasError = true;
492
                        }
493
                    }
494
                } else {
495
                    // Check to see if this important token is the same as the
496
                    // next important token in the pattern. If it is not, then
497
                    // the pattern cannot be for this piece of code.
498
                    $next = $phpcsFile->findNext(
499
                        $ignoreTokens,
500
                        $stackPtr,
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffer\Files\File::findNext() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
501
                        null,
502
                        true
503
                    );
504
505 View Code Duplication
                    if ($next === 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...
506
                        || $tokens[$next]['code'] !== $pattern[$i]['token']
507
                    ) {
508
                        // The next important token did not match the pattern.
509
                        return false;
510
                    }
511
512
                    if ($lastAddedStackPtr !== null) {
513 View Code Duplication
                        if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
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...
514
                            || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
515
                            && isset($tokens[$next]['scope_condition']) === true
516
                            && $tokens[$next]['scope_condition'] > $lastAddedStackPtr
517
                        ) {
518
                            // This is a brace, but the owner of it is after the current
519
                            // token, which means it does not belong to any token in
520
                            // our pattern. This means the pattern is not for us.
521
                            return false;
522
                        }
523
524 View Code Duplication
                        if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
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...
525
                            || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
526
                            && isset($tokens[$next]['parenthesis_owner']) === true
527
                            && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
528
                        ) {
529
                            // This is a bracket, but the owner of it is after the current
530
                            // token, which means it does not belong to any token in
531
                            // our pattern. This means the pattern is not for us.
532
                            return false;
533
                        }
534
                    }//end if
535
536
                    // If we skipped past some whitespace tokens, then add them
537
                    // to the found string.
538
                    if (($next - $stackPtr) > 0) {
539
                        $hasComment = false;
540
                        for ($j = $stackPtr; $j < $next; $j++) {
541
                            $found .= $tokens[$j]['content'];
542
                            if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
543
                                $hasComment = true;
544
                            }
545
                        }
546
547
                        // If we are not ignoring comments, this additional
548
                        // whitespace or comment is not allowed. If we are
549
                        // ignoring comments, there needs to be at least one
550
                        // comment for this to be allowed.
551
                        if ($this->ignoreComments === false
552
                            || ($this->ignoreComments === true
553
                            && $hasComment === false)
554
                        ) {
555
                            $hasError = true;
556
                        }
557
558
                        // Even when ignoring comments, we are not allowed to include
559
                        // newlines without the pattern specifying them, so
560
                        // everything should be on the same line.
561 View Code Duplication
                        if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
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...
562
                            $hasError = true;
563
                        }
564
                    }//end if
565
566
                    if ($next !== $lastAddedStackPtr) {
567
                        $found            .= $tokens[$next]['content'];
568
                        $lastAddedStackPtr = $next;
569
                    }
570
571
                    if (isset($pattern[($i + 1)]) === true
572
                        && $pattern[($i + 1)]['type'] === 'skip'
573
                    ) {
574
                        $stackPtr = $next;
575
                    } else {
576
                        $stackPtr = ($next + 1);
577
                    }
578
                }//end if
579
            } else if ($pattern[$i]['type'] === 'skip') {
580
                if ($pattern[$i]['to'] === 'unknown') {
581
                    $next = $phpcsFile->findNext(
582
                        $pattern[($i + 1)]['token'],
583
                        $stackPtr
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffer\Files\File::findNext() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
584
                    );
585
586
                    if ($next === false) {
587
                        // Couldn't find the next token, so we must
588
                        // be using the wrong pattern.
589
                        return false;
590
                    }
591
592
                    $found   .= '...';
593
                    $stackPtr = $next;
594
                } else {
595
                    // Find the previous opener.
596
                    $next = $phpcsFile->findPrevious(
597
                        Tokens::$blockOpeners,
598
                        $stackPtr
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffe...es\File::findPrevious() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
599
                    );
600
601
                    if ($next === false
602
                        || isset($tokens[$next][$pattern[$i]['to']]) === false
603
                    ) {
604
                        // If there was not opener, then we must
605
                        // be using the wrong pattern.
606
                        return false;
607
                    }
608
609
                    $found .= '...';
610
                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
611
                        $found .= ')';
612
                    } else {
613
                        $found .= '}';
614
                    }
615
616
                    // Skip to the closing token.
617
                    $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
618
                }//end if
619
            } else if ($pattern[$i]['type'] === 'string') {
620
                if ($tokens[$stackPtr]['code'] !== T_STRING) {
621
                    $hasError = true;
622
                }
623
624
                if ($stackPtr !== $lastAddedStackPtr) {
625
                    $found            .= 'abc';
626
                    $lastAddedStackPtr = $stackPtr;
627
                }
628
629
                $stackPtr++;
630
            } else if ($pattern[$i]['type'] === 'newline') {
631
                // Find the next token that contains a newline character.
632
                $newline = 0;
633
                for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
634
                    if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
635
                        $newline = $j;
636
                        break;
637
                    }
638
                }
639
640
                if ($newline === 0) {
641
                    // We didn't find a newline character in the rest of the file.
642
                    $next     = ($phpcsFile->numTokens - 1);
643
                    $hasError = true;
644
                } else {
645
                    if ($this->ignoreComments === false) {
646
                        // The newline character cannot be part of a comment.
647
                        if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
648
                            $hasError = true;
649
                        }
650
                    }
651
652
                    if ($newline === $stackPtr) {
653
                        $next = ($stackPtr + 1);
654
                    } else {
655
                        // Check that there were no significant tokens that we
656
                        // skipped over to find our newline character.
657
                        $next = $phpcsFile->findNext(
658
                            $ignoreTokens,
659
                            $stackPtr,
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffer\Files\File::findNext() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
660
                            null,
661
                            true
662
                        );
663
664
                        if ($next < $newline) {
665
                            // We skipped a non-ignored token.
666
                            $hasError = true;
667
                        } else {
668
                            $next = ($newline + 1);
669
                        }
670
                    }
671
                }//end if
672
673
                if ($stackPtr !== $lastAddedStackPtr) {
674
                    $found .= $phpcsFile->getTokensAsString(
675
                        $stackPtr,
0 ignored issues
show
Bug introduced by
It seems like $stackPtr can also be of type double or false; however, Symplify\PHP7_CodeSniffe...le::getTokensAsString() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
676
                        ($next - $stackPtr)
677
                    );
678
679
                    $diff = ($next - $stackPtr);
0 ignored issues
show
Unused Code introduced by
$diff is not used, you could remove the assignment.

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

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

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

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

Loading history...
680
                    $lastAddedStackPtr = ($next - 1);
681
                }
682
683
                $stackPtr = $next;
684
            }//end if
685
        }//end for
686
687
        if ($hasError === true) {
688
            $error = $this->prepareError($found, $patternCode);
689
            $errors[$origStackPtr] = $error;
690
        }
691
692
        return $errors;
693
694
    }//end processPattern()
695
696
697
    /**
698
     * Prepares an error for the specified patternCode.
699
     *
700
     * @param string $found       The actual found string in the code.
701
     * @param string $patternCode The expected pattern code.
702
     *
703
     * @return string The error message.
704
     */
705
    protected function prepareError($found, $patternCode)
706
    {
707
        $found    = str_replace("\r\n", '\n', $found);
708
        $found    = str_replace("\n", '\n', $found);
709
        $found    = str_replace("\r", '\n', $found);
710
        $found    = str_replace("\t", '\t', $found);
711
        $found    = str_replace('EOL', '\n', $found);
712
        $expected = str_replace('EOL', '\n', $patternCode);
713
714
        $error = "Expected \"$expected\"; found \"$found\"";
715
716
        return $error;
717
718
    }//end prepareError()
719
720
721
    /**
722
     * Returns the patterns that should be checked.
723
     *
724
     * @return string[]
725
     */
726
    abstract protected function getPatterns();
727
728
729
    /**
730
     * Registers any supplementary tokens that this test might wish to process.
731
     *
732
     * A sniff may wish to register supplementary tests when it wishes to group
733
     * an arbitrary validation that cannot be performed using a pattern, with
734
     * other pattern tests.
735
     *
736
     * @return int[]
737
     * @see    processSupplementary()
738
     */
739
    protected function registerSupplementary()
740
    {
741
        return array();
742
743
    }//end registerSupplementary()
744
745
746
     /**
747
      * Processes any tokens registered with registerSupplementary().
748
      *
749
      * @param Symplify\PHP7_CodeSniffer_File $phpcsFile The Symplify\PHP7_CodeSniffer file where to
750
      *                                        process the skip.
751
      * @param int                  $stackPtr  The position in the tokens stack to
752
      *                                        process.
753
      *
754
      * @return void
755
      * @see    registerSupplementary()
756
      */
757
    protected function processSupplementary(File $phpcsFile, $stackPtr)
0 ignored issues
show
Unused Code introduced by
The parameter $phpcsFile 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...
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...
758
    {
759
760
    }//end processSupplementary()
761
762
763
    /**
764
     * Parses a pattern string into an array of pattern steps.
765
     *
766
     * @param string $pattern The pattern to parse.
767
     *
768
     * @return array The parsed pattern array.
769
     * @see    createSkipPattern()
770
     * @see    createTokenPattern()
771
     */
772
    private function parse($pattern)
773
    {
774
        $patterns   = array();
775
        $length     = strlen($pattern);
776
        $lastToken  = 0;
777
        $firstToken = 0;
778
779
        for ($i = 0; $i < $length; $i++) {
780
            $specialPattern = false;
781
            $isLastChar     = ($i === ($length - 1));
782
            $oldFirstToken  = $firstToken;
783
784
            if (substr($pattern, $i, 3) === '...') {
785
                // It's a skip pattern. The skip pattern requires the
786
                // content of the token in the "from" position and the token
787
                // to skip to.
788
                $specialPattern = $this->createSkipPattern($pattern, ($i - 1));
789
                $lastToken      = ($i - $firstToken);
790
                $firstToken     = ($i + 3);
791
                $i = ($i + 2);
792
793
                if ($specialPattern['to'] !== 'unknown') {
794
                    $firstToken++;
795
                }
796
            } else if (substr($pattern, $i, 3) === 'abc') {
797
                $specialPattern = array('type' => 'string');
798
                $lastToken      = ($i - $firstToken);
799
                $firstToken     = ($i + 3);
800
                $i = ($i + 2);
801
            } else if (substr($pattern, $i, 3) === 'EOL') {
802
                $specialPattern = array('type' => 'newline');
803
                $lastToken      = ($i - $firstToken);
804
                $firstToken     = ($i + 3);
805
                $i = ($i + 2);
806
            }//end if
807
808
            if ($specialPattern !== false || $isLastChar === true) {
809
                // If we are at the end of the string, don't worry about a limit.
810
                if ($isLastChar === true) {
811
                    // Get the string from the end of the last skip pattern, if any,
812
                    // to the end of the pattern string.
813
                    $str = substr($pattern, $oldFirstToken);
814
                } else {
815
                    // Get the string from the end of the last special pattern,
816
                    // if any, to the start of this special pattern.
817
                    if ($lastToken === 0) {
818
                        // Note that if the last special token was zero characters ago,
819
                        // there will be nothing to process so we can skip this bit.
820
                        // This happens if you have something like: EOL... in your pattern.
821
                        $str = '';
822
                    } else {
823
                        $str = substr($pattern, $oldFirstToken, $lastToken);
824
                    }
825
                }
826
827
                if ($str !== '') {
828
                    $tokenPatterns = $this->createTokenPattern($str);
829
                    foreach ($tokenPatterns as $tokenPattern) {
830
                        $patterns[] = $tokenPattern;
831
                    }
832
                }
833
834
                // Make sure we don't skip the last token.
835
                if ($isLastChar === false && $i === ($length - 1)) {
836
                    $i--;
837
                }
838
            }//end if
839
840
            // Add the skip pattern *after* we have processed
841
            // all the tokens from the end of the last skip pattern
842
            // to the start of this skip pattern.
843
            if ($specialPattern !== false) {
844
                $patterns[] = $specialPattern;
845
            }
846
        }//end for
847
848
        return $patterns;
849
850
    }//end parse()
851
852
853
    /**
854
     * Creates a skip pattern.
855
     *
856
     * @param string $pattern The pattern being parsed.
857
     * @param string $from    The token content that the skip pattern starts from.
858
     *
859
     * @return array The pattern step.
860
     * @see    createTokenPattern()
861
     * @see    parse()
862
     */
863
    private function createSkipPattern($pattern, $from)
864
    {
865
        $skip = array('type' => 'skip');
866
867
        $nestedParenthesis = 0;
868
        $nestedBraces      = 0;
869
        for ($start = $from; $start >= 0; $start--) {
870
            switch ($pattern[$start]) {
871
            case '(':
872
                if ($nestedParenthesis === 0) {
873
                    $skip['to'] = 'parenthesis_closer';
874
                }
875
876
                $nestedParenthesis--;
877
                break;
878
            case '{':
879
                if ($nestedBraces === 0) {
880
                    $skip['to'] = 'scope_closer';
881
                }
882
883
                $nestedBraces--;
884
                break;
885
            case '}':
886
                $nestedBraces++;
887
                break;
888
            case ')':
889
                $nestedParenthesis++;
890
                break;
891
            }//end switch
892
893
            if (isset($skip['to']) === true) {
894
                break;
895
            }
896
        }//end for
897
898
        if (isset($skip['to']) === false) {
899
            $skip['to'] = 'unknown';
900
        }
901
902
        return $skip;
903
904
    }//end createSkipPattern()
905
906
907
    /**
908
     * Creates a token pattern.
909
     *
910
     * @param string $str The tokens string that the pattern should match.
911
     *
912
     * @return array The pattern step.
913
     * @see    createSkipPattern()
914
     * @see    parse()
915
     */
916
    private function createTokenPattern($str)
917
    {
918
        // Don't add a space after the closing php tag as it will add a new
919
        // whitespace token.
920
        $tokenizer = new PHP('<?php '.$str.'?>', null);
921
922
        // Remove the <?php tag from the front and the end php tag from the back.
923
        $tokens = $tokenizer->getTokens();
924
        $tokens = array_slice($tokens, 1, (count($tokens) - 2));
925
926
        $patterns = array();
927
        foreach ($tokens as $patternInfo) {
928
            $patterns[] = array(
929
                           'type'  => 'token',
930
                           'token' => $patternInfo['code'],
931
                           'value' => $patternInfo['content'],
932
                          );
933
        }
934
935
        return $patterns;
936
937
    }//end createTokenPattern()
938
939
940
}//end class
941