Completed
Push — WAIT-634-635/php7.0-new-list-a... ( 008d59 )
by Juliette
05:48 queued 03:52
created

AssignmentOrderSniff::process()   D

Complexity

Conditions 21
Paths 225

Size

Total Lines 126
Code Lines 60

Duplication

Lines 7
Ratio 5.56 %

Importance

Changes 0
Metric Value
dl 7
loc 126
rs 4.0194
c 0
b 0
f 0
cc 21
eloc 60
nc 225
nop 2

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
 * \PHPCompatibility\Sniffs\Lists\AssignmentOrderSniff.
4
 *
5
 * PHP version 7.0
6
 *
7
 * @category PHP
8
 * @package  PHPCompatibility\Lists
9
 * @author   Juliette Reinders Folmer <[email protected]>
10
 */
11
12
namespace PHPCompatibility\Sniffs\Lists;
13
14
use PHPCompatibility\Sniff;
15
use PHPCompatibility\PHPCSHelper;
16
17
/**
18
 * \PHPCompatibility\Sniffs\Lists\AssignmentOrderSniff.
19
 *
20
 * The list() construct no longer assigns variables in reverse order.
21
 * This affects all list constructs where non-unique variables are used.
22
 *
23
 * PHP version 7.0
24
 *
25
 * @category PHP
26
 * @package  PHPCompatibility\Lists
27
 * @author   Juliette Reinders Folmer <[email protected]>
28
 */
29
class AssignmentOrderSniff extends Sniff
30
{
31
32
    /**
33
     * Returns an array of tokens this test wants to listen for.
34
     *
35
     * @return array
36
     */
37
    public function register()
38
    {
39
        return array(
40
            T_LIST,
41
            T_OPEN_SHORT_ARRAY,
42
        );
43
    }
44
45
46
    /**
47
     * Processes this test, when one of its tokens is encountered.
48
     *
49
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
50
     * @param int                   $stackPtr  The position of the current token in the
51
     *                                         stack passed in $tokens.
52
     *
53
     * @return void|int Void if not a valid list. If a list construct has been
54
     *                  examined, the stack pointer to the list closer to skip
55
     *                  passed any nested lists which don't need to be examined again.
56
     */
57
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
58
    {
59
        if ($this->supportsAbove('7.0') === false) {
60
            return;
61
        }
62
63
        $tokens = $phpcsFile->getTokens();
64
65
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY
66
            && $this->isShortList($phpcsFile, $stackPtr) === false
67
        ) {
68
            // Short array, not short list.
69
            return;
70
        }
71
72
        if ($tokens[$stackPtr]['code'] === T_LIST) {
73
            $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
74 View Code Duplication
            if ($nextNonEmpty === false
75
                || $tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS
76
                || isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false
77
            ) {
78
                // Parse error or live coding.
79
                return;
80
            }
81
82
            $opener = $nextNonEmpty;
83
            $closer = $tokens[$nextNonEmpty]['parenthesis_closer'];
84
        } else {
85
            // Short list syntax.
86
            $opener = $stackPtr;
87
88
            if (isset($tokens[$stackPtr]['bracket_closer'])) {
89
                $closer = $tokens[$stackPtr]['bracket_closer'];
90
            }
91
        }
92
93
        if (isset($opener, $closer) === false) {
94
            return;
95
        }
96
97
        /*
98
         * OK, so we have the opener & closer, now we need to check all the variables in the
99
         * list() to see if there are duplicates as that's the problem.
100
         */
101
        $hasVars = $phpcsFile->findNext(array(T_VARIABLE, T_DOLLAR), ($opener + 1), $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...
102
        if ($hasVars === false) {
103
            // Empty list, not our concern.
104
            return ($closer + 1);
105
        }
106
107
        // Set the variable delimiters based on the list type being examined.
108
        $stopPoints = array(T_COMMA);
109
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY) {
110
            $stopPoints[] = T_CLOSE_SHORT_ARRAY;
111
        } else {
112
            $stopPoints[] = T_CLOSE_PARENTHESIS;
113
        }
114
115
        $listVars      = array();
116
        $emptyTokens   = array_flip(\PHP_CodeSniffer_Tokens::$emptyTokens);
117
        $lastStopPoint = $opener;
118
119
        /*
120
         * Create a list of all variables used within the `list()` construct.
121
         * We're not concerned with whether these are nested or not, as any duplicate
122
         * variable name used will be problematic, independent of nesting.
123
         */
124
        do {
125
            $nextStopPoint = $phpcsFile->findNext($stopPoints, ($lastStopPoint + 1), $closer);
126
            if ($nextStopPoint === false) {
127
                $nextStopPoint = $closer;
128
            }
129
130
            // Also detect this in PHP 7.1 keyed lists.
131
            $hasDoubleArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($lastStopPoint + 1), $nextStopPoint);
132
            if ($hasDoubleArrow !== false) {
133
                $lastStopPoint = $hasDoubleArrow;
134
            }
135
136
            // Find the start of the variable, allowing for variable variables.
137
            $nextStartPoint = $phpcsFile->findNext(array(T_VARIABLE, T_DOLLAR), ($lastStopPoint + 1), $nextStopPoint);
138
            if ($nextStartPoint === false) {
139
                // Skip past empty bits in the list, i.e. `list( $a, , ,)`.
140
                $lastStopPoint = $nextStopPoint;
141
                continue;
142
            }
143
144
            /*
145
             * Gather the content of all non-empty tokens to determine the "variable name".
146
             * Variable name in this context includes array or object property syntaxes, such
147
             * as `$a['name']` and `$b->property`.
148
             */
149
            $varContent = '';
150
151
            for ($i = $nextStartPoint; $i < $nextStopPoint; $i++) {
152
                if (isset($emptyTokens[$tokens[$i]['code']])) {
153
                    continue;
154
                }
155
156
                $varContent .= $tokens[$i]['content'];
157
            }
158
159
            if ($varContent !== '') {
160
                $listVars[] = $varContent;
161
            }
162
163
            $lastStopPoint = $nextStopPoint;
164
165
        } while ($lastStopPoint < $closer);
166
167
        if (empty($listVars)) {
168
            // Shouldn't be possible, but just in case.
169
            return ($closer + 1);
170
        }
171
172
        // Verify that all variables used in the list() construct are unique.
173
        if (count($listVars) !== count(array_unique($listVars))) {
174
            $phpcsFile->addError(
175
                'list() will assign variable from left-to-right since PHP 7.0. Ensure all variables in list() are unique to prevent unexpected results.',
176
                $stackPtr,
177
                'Affected'
178
            );
179
        }
180
181
        return ($closer + 1);
182
    }
183
}
184