Completed
Push — master ( c0535c...efa94f )
by Juliette
35s
created

AssignmentOrderSniff   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 154
Duplicated Lines 16.88 %

Coupling/Cohesion

Components 0
Dependencies 1

Importance

Changes 0
Metric Value
wmc 22
lcom 0
cbo 1
dl 26
loc 154
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 7 1
F process() 26 125 21

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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