Completed
Pull Request — master (#655)
by Juliette
02:09
created

NewKeyedListSniff::process()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 42
Code Lines 22

Duplication

Lines 7
Ratio 16.67 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 7
loc 42
rs 4.8196
cc 10
eloc 22
nc 9
nop 2

How to fix   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
 * \PHPCompatibility\Sniffs\Lists\NewKeyedListSniff.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @category PHP
8
 * @package  PHPCompatibility
9
 * @author   Juliette Reinders Folmer <[email protected]>
10
 */
11
12
namespace PHPCompatibility\Sniffs\Lists;
13
14
use PHPCompatibility\Sniff;
15
16
/**
17
 * \PHPCompatibility\Sniffs\Lists\NewKeyedListSniff.
18
 *
19
 * "You can now specify keys in list(), or its new shorthand [] syntax. "
20
 *
21
 * PHP version 7.1
22
 *
23
 * @category PHP
24
 * @package  PHPCompatibility
25
 * @author   Juliette Reinders Folmer <[email protected]>
26
 */
27
class NewKeyedListSniff extends Sniff
28
{
29
30
    /**
31
     * Returns an array of tokens this test wants to listen for.
32
     *
33
     * @return array
34
     */
35
    public function register()
36
    {
37
        return array(
38
            T_LIST,
39
            T_OPEN_SHORT_ARRAY,
40
        );
41
    }
42
43
    /**
44
     * Do a version check to determine if this sniff needs to run at all.
45
     *
46
     * @return bool
47
     */
48
    protected function bowOutEarly()
49
    {
50
        return ($this->supportsBelow('7.0') === false);
51
    }
52
53
    /**
54
     * Processes this test, when one of its tokens is encountered.
55
     *
56
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
57
     * @param int                   $stackPtr  The position of the current token in the
58
     *                                         stack passed in $tokens.
59
     *
60
     * @return void
61
     */
62
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
63
    {
64
        if ($this->bowOutEarly() === true) {
65
            return;
66
        }
67
68
        $tokens = $phpcsFile->getTokens();
69
70
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY
71
            && $this->isShortList($phpcsFile, $stackPtr) === false
72
        ) {
73
            // Short array, not short list.
74
            return;
75
        }
76
77
        if ($tokens[$stackPtr]['code'] === T_LIST) {
78
            $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
79 View Code Duplication
            if ($nextNonEmpty === false
80
                || $tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS
81
                || isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false
82
            ) {
83
                // Parse error or live coding.
84
                return;
85
            }
86
87
            $opener = $nextNonEmpty;
88
            $closer = $tokens[$nextNonEmpty]['parenthesis_closer'];
89
        } else {
90
            // Short list syntax.
91
            $opener = $stackPtr;
92
93
            if (isset($tokens[$stackPtr]['bracket_closer'])) {
94
                $closer = $tokens[$stackPtr]['bracket_closer'];
95
            }
96
        }
97
98
        if (isset($opener, $closer) === false) {
99
            return;
100
        }
101
102
        $this->examineList($phpcsFile, $opener, $closer);
0 ignored issues
show
Bug introduced by
The variable $closer does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
103
    }
104
105
106
    /**
107
     * Examine the contents of a list construct to determine whether an error needs to be thrown.
108
     *
109
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
110
     * @param int                   $opener    The position of the list open token.
111
     * @param int                   $closer    The position of the list close token.
112
     *
113
     * @return void
114
     */
115
    protected function examineList(\PHP_CodeSniffer_File $phpcsFile, $opener, $closer)
116
    {
117
        $targets = array(T_DOUBLE_ARROW);
118
        $start   = $opener;
119
        while (($start = $this->hasTargetInList($phpcsFile, $start, $closer, $targets)) !== false) {
120
            $phpcsFile->addError(
121
                'Specifying keys in list constructs is not supported in PHP 7.0 or earlier.',
122
                $start,
123
                'Found'
124
            );
125
        }
126
    }
127
128
129
    /**
130
     * Check whether a certain target token exists within a list construct.
131
     *
132
     * Skips past nested list constructs, so these can be examined based on their own token.
133
     *
134
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
135
     * @param int                   $start     The position of the list open token or a token
136
     *                                         within the list to start (resume) the examination from.
137
     * @param int                   $closer    The position of the list close token.
138
     * @param array                 $targets   An array with one or more token constants to look for.
139
     *
140
     * @return int|bool Stack pointer to the target token if encountered. False otherwise.
141
     */
142
    protected function hasTargetInList(\PHP_CodeSniffer_File $phpcsFile, $start, $closer, $targets)
143
    {
144
        $tokens     = $phpcsFile->getTokens();
145
        $targets    = array_flip($targets);
146
        $allTargets = $targets + array_flip($this->register());
147
148
        for ($i = ($start + 1); $i < $closer; $i++) {
149
            if (isset($allTargets[$tokens[$i]['code']]) === false) {
150
                continue;
151
            }
152
153
            if (isset($targets[$tokens[$i]['code']]) === true) {
154
                return $i;
155
            }
156
157
            // Skip past nested list constructs.
158
            if ($tokens[$i]['code'] === T_LIST) {
159
                $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true);
160
                if ($nextNonEmpty !== false
161
                    && $tokens[$nextNonEmpty]['code'] === T_OPEN_PARENTHESIS
162
                    && isset($tokens[$nextNonEmpty]['parenthesis_closer']) === true
163
                ) {
164
                    $i = $tokens[$nextNonEmpty]['parenthesis_closer'];
165
                }
166 View Code Duplication
            } elseif ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY
167
                && isset($tokens[$i]['bracket_closer'])
168
            ) {
169
                $i = $tokens[$i]['bracket_closer'];
170
            }
171
        }
172
173
        return false;
174
    }
175
}
176