Completed
Push — master ( 06cc8d...999934 )
by Juliette
20s queued 10s
created

RemovedImplodeFlexibleParamOrderSniff   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 295
Duplicated Lines 6.78 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 1
dl 20
loc 295
rs 8.5599
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A bowOutEarly() 0 4 1
F processParameters() 20 168 46
A throwNotice() 0 21 1

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RemovedImplodeFlexibleParamOrderSniff often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RemovedImplodeFlexibleParamOrderSniff, and based on these observations, apply Extract Interface, too.

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\ParameterValues;
12
13
use PHPCompatibility\AbstractFunctionCallParameterSniff;
14
use PHP_CodeSniffer_File as File;
15
use PHP_CodeSniffer_Tokens as Tokens;
16
17
/**
18
 * Passing the $glue and $pieces parameters to implode() in reverse order has been
19
 * deprecated in PHP 7.4.
20
 *
21
 * @link https://wiki.php.net/rfc/deprecations_php_7_4#implode_parameter_order_mix
22
 * @link https://php.net/manual/en/function.implode.php
23
 *
24
 * PHP version 7.4
25
 *
26
 * @since 9.3.0
27
 */
28
class RemovedImplodeFlexibleParamOrderSniff extends AbstractFunctionCallParameterSniff
29
{
30
31
    /**
32
     * Functions to check for.
33
     *
34
     * @since 9.3.0
35
     *
36
     * @var array
37
     */
38
    protected $targetFunctions = array(
39
        'implode' => true,
40
        'join'    => true,
41
    );
42
43
    /**
44
     * List of PHP native constants which should be recognized as text strings.
45
     *
46
     * @since 9.3.0
47
     *
48
     * @var array
49
     */
50
    private $constantStrings = array(
51
        'DIRECTORY_SEPARATOR' => true,
52
        'PHP_EOL'             => true,
53
    );
54
55
    /**
56
     * List of PHP native functions which should be recognized as returning an array.
57
     *
58
     * Note: The array_*() functions will always be taken into account.
59
     *
60
     * @since 9.3.0
61
     *
62
     * @var array
63
     */
64
    private $arrayFunctions = array(
65
        'compact' => true,
66
        'explode' => true,
67
        'range'   => true,
68
    );
69
70
    /**
71
     * List of PHP native array functions which should *not* be recognized as returning an array.
72
     *
73
     * @since 9.3.0
74
     *
75
     * @var array
76
     */
77
    private $arrayFunctionExceptions = array(
78
        'array_key_exists'     => true,
79
        'array_key_first'      => true,
80
        'array_key_last'       => true,
81
        'array_multisort'      => true,
82
        'array_pop'            => true,
83
        'array_product'        => true,
84
        'array_push'           => true,
85
        'array_search'         => true,
86
        'array_shift'          => true,
87
        'array_sum'            => true,
88
        'array_unshift'        => true,
89
        'array_walk_recursive' => true,
90
        'array_walk'           => true,
91
    );
92
93
94
    /**
95
     * Do a version check to determine if this sniff needs to run at all.
96
     *
97
     * @since 9.3.0
98
     *
99
     * @return bool
100
     */
101
    protected function bowOutEarly()
102
    {
103
        return ($this->supportsAbove('7.4') === false);
104
    }
105
106
107
    /**
108
     * Process the parameters of a matched function.
109
     *
110
     * @since 9.3.0
111
     *
112
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
113
     * @param int                   $stackPtr     The position of the current token in the stack.
114
     * @param string                $functionName The token content (function name) which was matched.
115
     * @param array                 $parameters   Array with information about the parameters.
116
     *
117
     * @return int|void Integer stack pointer to skip forward or void to continue
118
     *                  normal file processing.
119
     */
120
    public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
121
    {
122
        if (isset($parameters[2]) === false) {
123
            // Only one parameter, this must be $pieces. Bow out.
124
            return;
125
        }
126
127
        $tokens = $phpcsFile->getTokens();
128
129
        /*
130
         * Examine the first parameter.
131
         * If there is any indication that this is an array declaration, we have an error.
132
         */
133
134
        $targetParam = $parameters[1];
135
        $start       = $targetParam['start'];
136
        $end         = ($targetParam['end'] + 1);
137
        $isOnlyText  = true;
138
139
        $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
140
        if ($firstNonEmpty === false) {
141
            // Parse error. Shouldn't be possible.
142
            return;
143
        }
144
145 View Code Duplication
        if ($tokens[$firstNonEmpty]['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...
146
            $start = ($firstNonEmpty + 1);
147
            $end   = $tokens[$firstNonEmpty]['parenthesis_closer'];
148
        }
149
150
        $hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end);
151 View Code Duplication
        if ($hasTernary !== 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...
152
            && isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis'])
153
            && count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis'])
154
        ) {
155
            $start = ($hasTernary + 1);
156
        }
157
158
        for ($i = $start; $i < $end; $i++) {
159
            $tokenCode = $tokens[$i]['code'];
160
161
            if (isset(Tokens::$emptyTokens[$tokenCode])) {
162
                continue;
163
            }
164
165
            if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) {
166
                continue;
167
            }
168
169
            if ($hasTernary !== false && $tokenCode === \T_INLINE_ELSE) {
170
                continue;
171
            }
172
173
            if (isset(Tokens::$stringTokens[$tokenCode]) === false) {
174
                $isOnlyText = false;
175
            }
176
177
            if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) {
178
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
179
                return;
180
            }
181
182
            if ($tokenCode === \T_STRING) {
183
                /*
184
                 * Check for specific functions which return an array (i.e. $pieces).
185
                 */
186
                $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), $end, true);
187
                if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
188
                    continue;
189
                }
190
191
                $nameLc = strtolower($tokens[$i]['content']);
192
                if (isset($this->arrayFunctions[$nameLc]) === false
193
                    && (strpos($nameLc, 'array_') !== 0
194
                    || isset($this->arrayFunctionExceptions[$nameLc]) === true)
195
                ) {
196
                    continue;
197
                }
198
199
                // Now make sure it's the PHP native function being called.
200
                $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($i - 1), $start, true);
201
                if ($tokens[$prevNonEmpty]['code'] === \T_DOUBLE_COLON
202
                    || $tokens[$prevNonEmpty]['code'] === \T_OBJECT_OPERATOR
203
                ) {
204
                    // Method call, not a call to the PHP native function.
205
                    continue;
206
                }
207
208
                if ($tokens[$prevNonEmpty]['code'] === \T_NS_SEPARATOR
209
                    && $tokens[$prevNonEmpty - 1]['code'] === \T_STRING
210
                ) {
211
                    // Namespaced function.
212
                    continue;
213
                }
214
215
                // Ok, so we know that there is an array function in the first param.
216
                // 99.9% chance that this is $pieces, not $glue.
217
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
218
                return;
219
            }
220
        }
221
222
        if ($isOnlyText === true) {
223
            // First parameter only contained text string tokens, i.e. glue.
224
            return;
225
        }
226
227
        /*
228
         * Examine the second parameter.
229
         */
230
231
        $targetParam = $parameters[2];
232
        $start       = $targetParam['start'];
233
        $end         = ($targetParam['end'] + 1);
234
235
        $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $end, true);
236
        if ($firstNonEmpty === false) {
237
            // Parse error. Shouldn't be possible.
238
            return;
239
        }
240
241 View Code Duplication
        if ($tokens[$firstNonEmpty]['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...
242
            $start = ($firstNonEmpty + 1);
243
            $end   = $tokens[$firstNonEmpty]['parenthesis_closer'];
244
        }
245
246
        $hasTernary = $phpcsFile->findNext(\T_INLINE_THEN, $start, $end);
247 View Code Duplication
        if ($hasTernary !== 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...
248
            && isset($tokens[$start]['nested_parenthesis'], $tokens[$hasTernary]['nested_parenthesis'])
249
            && count($tokens[$start]['nested_parenthesis']) === count($tokens[$hasTernary]['nested_parenthesis'])
250
        ) {
251
            $start = ($hasTernary + 1);
252
        }
253
254
        for ($i = $start; $i < $end; $i++) {
255
            $tokenCode = $tokens[$i]['code'];
256
257
            if (isset(Tokens::$emptyTokens[$tokenCode])) {
258
                continue;
259
            }
260
261
            if ($tokenCode === \T_ARRAY || $tokenCode === \T_OPEN_SHORT_ARRAY || $tokenCode === \T_ARRAY_CAST) {
262
                // Found an array, $pieces is second.
263
                return;
264
            }
265
266
            if ($tokenCode === \T_STRING && isset($this->constantStrings[$tokens[$i]['content']])) {
267
                // One of the special cased, PHP native string constants found.
268
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
269
                return;
270
            }
271
272
            if ($tokenCode === \T_STRING || $tokenCode === \T_VARIABLE) {
273
                // Function call, constant or variable encountered.
274
                // No matter what this is combined with, we won't be able to reliably determine the value.
275
                return;
276
            }
277
278
            if ($tokenCode === \T_CONSTANT_ENCAPSED_STRING
279
                || $tokenCode === \T_DOUBLE_QUOTED_STRING
280
                || $tokenCode === \T_HEREDOC
281
                || $tokenCode === \T_NOWDOC
282
            ) {
283
                $this->throwNotice($phpcsFile, $stackPtr, $functionName);
284
                return;
285
            }
286
        }
287
    }
288
289
290
    /**
291
     * Throw the error/warning.
292
     *
293
     * @since 9.3.0
294
     *
295
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
296
     * @param int                   $stackPtr     The position of the current token in the stack.
297
     * @param string                $functionName The token content (function name) which was matched.
298
     *
299
     * @return void
300
     */
301
    protected function throwNotice(File $phpcsFile, $stackPtr, $functionName)
302
    {
303
        $message   = 'Passing the $glue and $pieces parameters in reverse order to %s has been deprecated since PHP 7.4';
304
        $isError   = false;
305
        $errorCode = 'Deprecated';
306
        $data      = array($functionName);
307
308
        /*
309
        Support for the deprecated behaviour is expected to be removed in PHP 8.0.
310
        Once this has been implemented, this section should be uncommented.
311
        if ($this->supportsAbove('8.0') === true) {
312
            $message  .= ' and is removed since PHP 8.0';
313
            $isError   = true;
314
            $errorCode = 'Removed';
315
        }
316
        */
317
318
        $message .= '; $glue should be the first parameter and $pieces the second';
319
320
        $this->addMessage($phpcsFile, $message, $stackPtr, $isError, $errorCode, $data);
321
    }
322
}
323