DiscouragedSwitchContinueSniff::process()   F
last analyzed

Complexity

Conditions 27
Paths 165

Size

Total Lines 132

Duplication

Lines 3
Ratio 2.27 %

Importance

Changes 0
Metric Value
dl 3
loc 132
rs 2.9
c 0
b 0
f 0
cc 27
nc 165
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, an external standard for PHP_CodeSniffer.
4
 *
5
 * @package   PHPCompatibility
6
 * @copyright 2012-2019 PHPCompatibility Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCompatibility/PHPCompatibility
9
 */
10
11
namespace PHPCompatibility\Sniffs\ControlStructures;
12
13
use PHPCompatibility\Sniff;
14
use PHP_CodeSniffer_File as File;
15
use PHP_CodeSniffer_Tokens as Tokens;
16
17
/**
18
 * Detect use of `continue` in `switch` control structures.
19
 *
20
 * As of PHP 7.3, PHP will throw a warning when `continue` is used to target a `switch`
21
 * control structure.
22
 * The sniff takes numeric arguments used with `continue` into account.
23
 *
24
 * PHP version 7.3
25
 *
26
 * @link https://www.php.net/manual/en/migration73.incompatible.php#migration73.incompatible.core.continue-targeting-switch
27
 * @link https://wiki.php.net/rfc/continue_on_switch_deprecation
28
 * @link https://github.com/php/php-src/commit/04e3523b7d095341f65ed5e71a3cac82fca690e4
29
 *       (actual implementation which is different from the RFC).
30
 * @link https://www.php.net/manual/en/control-structures.switch.php
31
 *
32
 * @since 8.2.0
33
 */
34
class DiscouragedSwitchContinueSniff extends Sniff
35
{
36
37
    /**
38
     * Token codes of control structures which can be targeted using continue.
39
     *
40
     * @since 8.2.0
41
     *
42
     * @var array
43
     */
44
    protected $loopStructures = array(
45
        \T_FOR     => \T_FOR,
46
        \T_FOREACH => \T_FOREACH,
47
        \T_WHILE   => \T_WHILE,
48
        \T_DO      => \T_DO,
49
        \T_SWITCH  => \T_SWITCH,
50
    );
51
52
    /**
53
     * Tokens which start a new case within a switch.
54
     *
55
     * @since 8.2.0
56
     *
57
     * @var array
58
     */
59
    protected $caseTokens = array(
60
        \T_CASE    => \T_CASE,
61
        \T_DEFAULT => \T_DEFAULT,
62
    );
63
64
    /**
65
     * Token codes which are accepted to determine the level for the continue.
66
     *
67
     * This array is enriched with the arithmetic operators in the register() method.
68
     *
69
     * @since 8.2.0
70
     *
71
     * @var array
72
     */
73
    protected $acceptedLevelTokens = array(
74
        \T_LNUMBER           => \T_LNUMBER,
75
        \T_OPEN_PARENTHESIS  => \T_OPEN_PARENTHESIS,
76
        \T_CLOSE_PARENTHESIS => \T_CLOSE_PARENTHESIS,
77
    );
78
79
80
    /**
81
     * Returns an array of tokens this test wants to listen for.
82
     *
83
     * @since 8.2.0
84
     *
85
     * @return array
86
     */
87
    public function register()
88
    {
89
        $this->acceptedLevelTokens += Tokens::$arithmeticTokens;
90
        $this->acceptedLevelTokens += Tokens::$emptyTokens;
91
92
        return array(\T_SWITCH);
93
    }
94
95
    /**
96
     * Processes this test, when one of its tokens is encountered.
97
     *
98
     * @since 8.2.0
99
     *
100
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
101
     * @param int                   $stackPtr  The position of the current token in the
102
     *                                         stack passed in $tokens.
103
     *
104
     * @return void
105
     */
106
    public function process(File $phpcsFile, $stackPtr)
107
    {
108
        if ($this->supportsAbove('7.3') === false) {
109
            return;
110
        }
111
112
        $tokens = $phpcsFile->getTokens();
113
114 View Code Duplication
        if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === 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...
115
            return;
116
        }
117
118
        $switchOpener = $tokens[$stackPtr]['scope_opener'];
119
        $switchCloser = $tokens[$stackPtr]['scope_closer'];
120
121
        // Quick check whether we need to bother with the more complex logic.
122
        $hasContinue = $phpcsFile->findNext(\T_CONTINUE, ($switchOpener + 1), $switchCloser);
123
        if ($hasContinue === false) {
124
            return;
125
        }
126
127
        $caseDefault = $switchOpener;
128
129
        do {
130
            $caseDefault = $phpcsFile->findNext($this->caseTokens, ($caseDefault + 1), $switchCloser);
131
            if ($caseDefault === false) {
132
                break;
133
            }
134
135
            if (isset($tokens[$caseDefault]['scope_opener']) === false) {
136
                // Unknown start of the case, skip.
137
                continue;
138
            }
139
140
            $caseOpener      = $tokens[$caseDefault]['scope_opener'];
141
            $nextCaseDefault = $phpcsFile->findNext($this->caseTokens, ($caseDefault + 1), $switchCloser);
142
            if ($nextCaseDefault === false) {
143
                $caseCloser = $switchCloser;
144
            } else {
145
                $caseCloser = $nextCaseDefault;
146
            }
147
148
            // Check for unscoped control structures within the case.
149
            $controlStructure = $caseOpener;
150
            $doCount          = 0;
151
            while (($controlStructure = $phpcsFile->findNext($this->loopStructures, ($controlStructure + 1), $caseCloser)) !== false) {
152
                if ($tokens[$controlStructure]['code'] === \T_DO) {
153
                    $doCount++;
154
                }
155
156
                if (isset($tokens[$controlStructure]['scope_opener'], $tokens[$controlStructure]['scope_closer']) === false) {
157
                    if ($tokens[$controlStructure]['code'] === \T_WHILE && $doCount > 0) {
158
                        // While in a do-while construct.
159
                        $doCount--;
160
                        continue;
161
                    }
162
163
                    // Control structure without braces found within the case, ignore this case.
164
                    continue 2;
165
                }
166
            }
167
168
            // Examine the contents of the case.
169
            $continue = $caseOpener;
170
171
            do {
172
                $continue = $phpcsFile->findNext(\T_CONTINUE, ($continue + 1), $caseCloser);
173
                if ($continue === false) {
174
                    break;
175
                }
176
177
                $nextSemicolon = $phpcsFile->findNext(array(\T_SEMICOLON, \T_CLOSE_TAG), ($continue + 1), $caseCloser);
178
                $codeString    = '';
179
                for ($i = ($continue + 1); $i < $nextSemicolon; $i++) {
180
                    if (isset($this->acceptedLevelTokens[$tokens[$i]['code']]) === false) {
181
                        // Function call/variable or other token which make numeric level impossible to determine.
182
                        continue 2;
183
                    }
184
185
                    if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
186
                        continue;
187
                    }
188
189
                    $codeString .= $tokens[$i]['content'];
190
                }
191
192
                $level = null;
193
                if ($codeString !== '') {
194
                    if (is_numeric($codeString)) {
195
                        $level = (int) $codeString;
196
                    } else {
197
                        // With the above logic, the string can only contain digits and operators, eval!
198
                        $level = eval("return ( $codeString );");
199
                    }
200
                }
201
202
                if (isset($level) === false || $level === 0) {
203
                    $level = 1;
204
                }
205
206
                // Examine which control structure is being targeted by the continue statement.
207
                if (isset($tokens[$continue]['conditions']) === false) {
208
                    continue;
209
                }
210
211
                $conditions = array_reverse($tokens[$continue]['conditions'], true);
212
                // PHPCS adds more structures to the conditions array than we want to take into
213
                // consideration, so clean up the array.
214
                foreach ($conditions as $tokenPtr => $tokenCode) {
215
                    if (isset($this->loopStructures[$tokenCode]) === false) {
216
                        unset($conditions[$tokenPtr]);
217
                    }
218
                }
219
220
                $targetCondition = \array_slice($conditions, ($level - 1), 1, true);
221
                if (empty($targetCondition)) {
222
                    continue;
223
                }
224
225
                $conditionToken = key($targetCondition);
226
                if ($conditionToken === $stackPtr) {
227
                    $phpcsFile->addWarning(
228
                        "Targeting a 'switch' control structure with a 'continue' statement is strongly discouraged and will throw a warning as of PHP 7.3.",
229
                        $continue,
230
                        'Found'
231
                    );
232
                }
233
234
            } while ($continue < $caseCloser);
235
236
        } while ($caseDefault < $switchCloser);
237
    }
238
}
239