Completed
Push — feature/294-446-fix-bitshift-b... ( 3be3ae )
by Juliette
07:40
created

PHPCSHelper   D

Complexity

Total Complexity 81

Size/Duplication

Total Lines 499
Duplicated Lines 6.61 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 81
lcom 1
cbo 1
dl 33
loc 499
rs 4.8717
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getVersion() 0 10 2
A setConfigData() 0 10 2
A getConfigData() 0 10 2
C findEndOfStatement() 0 71 22
C findExtendedClassName() 6 47 9
C findImplementedInterfaceNames() 0 49 8
F getMethodParameters() 27 158 36

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 PHPCSHelper 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 PHPCSHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * PHPCS cross-version compatibility helper class.
4
 *
5
 * @category PHP
6
 * @package  PHPCompatibility
7
 * @author   Juliette Reinders Folmer <[email protected]>
8
 */
9
10
namespace PHPCompatibility;
11
12
/**
13
 * \PHPCompatibility\PHPCSHelper
14
 *
15
 * PHPCS cross-version compatibility helper class.
16
 *
17
 * A number of PHPCS classes were split up into several classes in PHPCS 3.x
18
 * Those classes cannot be aliased as they don't represent the same object.
19
 * This class provides helper methods for functions which were contained in
20
 * one of these classes and which are used within the PHPCompatibility library.
21
 *
22
 * @category PHP
23
 * @package  PHPCompatibility
24
 * @author   Juliette Reinders Folmer <[email protected]>
25
 */
26
class PHPCSHelper
27
{
28
29
    /**
30
     * Get the PHPCS version number.
31
     *
32
     * @return string
33
     */
34
    public static function getVersion()
35
    {
36
        if (defined('\PHP_CodeSniffer\Config::VERSION')) {
37
            // PHPCS 3.x.
38
            return \PHP_CodeSniffer\Config::VERSION;
39
        } else {
40
            // PHPCS 1.x & 2.x.
41
            return \PHP_CodeSniffer::VERSION;
42
        }
43
    }
44
45
46
    /**
47
     * Pass config data to PHPCS.
48
     *
49
     * PHPCS cross-version compatibility helper.
50
     *
51
     * @param string      $key   The name of the config value.
52
     * @param string|null $value The value to set. If null, the config entry
53
     *                           is deleted, reverting it to the default value.
54
     * @param boolean     $temp  Set this config data temporarily for this script run.
55
     *                           This will not write the config data to the config file.
56
     *
57
     * @return void
58
     */
59
    public static function setConfigData($key, $value, $temp = false)
60
    {
61
        if (method_exists('\PHP_CodeSniffer\Config', 'setConfigData')) {
62
            // PHPCS 3.x.
63
            \PHP_CodeSniffer\Config::setConfigData($key, $value, $temp);
64
        } else {
65
            // PHPCS 1.x & 2.x.
66
            \PHP_CodeSniffer::setConfigData($key, $value, $temp);
67
        }
68
    }
69
70
71
    /**
72
     * Get the value of a single PHPCS config key.
73
     *
74
     * @param string $key The name of the config value.
75
     *
76
     * @return string|null
77
     */
78
    public static function getConfigData($key)
79
    {
80
        if (method_exists('\PHP_CodeSniffer\Config', 'getConfigData')) {
81
            // PHPCS 3.x.
82
            return \PHP_CodeSniffer\Config::getConfigData($key);
83
        } else {
84
            // PHPCS 1.x & 2.x.
85
            return \PHP_CodeSniffer::getConfigData($key);
86
        }
87
    }
88
89
90
    /**
91
     * Returns the position of the last non-whitespace token in a statement.
92
     *
93
     * {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
94
     * class and introduced in PHPCS 2.x.
95
     *
96
     * Once the minimum supported PHPCS version for this standard goes beyond
97
     * that, this method can be removed and calls to it replaced with
98
     * `$phpcsFile->findEndOfStatement($start, $ignore)` calls.
99
     *
100
     * Last synced with PHPCS version: PHPCS 3.3.0-alpha at commit e2a3e465b6a6f7cbd50eaa8a6ed5c47dbe5de641}}
101
     *
102
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
103
     * @param int                   $start     The position to start searching from in the token stack.
104
     * @param int|array             $ignore    Token types that should not be considered stop points.
105
     *
106
     * @return int
107
     */
108
    public static function findEndOfStatement(\PHP_CodeSniffer_File $phpcsFile, $start, $ignore = null)
109
    {
110
        if (version_compare(self::getVersion(), '2.0.0', '>=') === true) {
111
            return $phpcsFile->findEndOfStatement($start, $ignore);
112
        }
113
114
        $tokens    = $phpcsFile->getTokens();
115
        $endTokens = array(
116
            T_COLON                => true,
117
            T_COMMA                => true,
118
            T_DOUBLE_ARROW         => true,
119
            T_SEMICOLON            => true,
120
            T_CLOSE_PARENTHESIS    => true,
121
            T_CLOSE_SQUARE_BRACKET => true,
122
            T_CLOSE_CURLY_BRACKET  => true,
123
            T_CLOSE_SHORT_ARRAY    => true,
124
            T_OPEN_TAG             => true,
125
            T_CLOSE_TAG            => true,
126
        );
127
128
        if ($ignore !== null) {
129
            $ignore = (array) $ignore;
130
            foreach ($ignore as $code) {
131
                if (isset($endTokens[$code]) === true) {
132
                    unset($endTokens[$code]);
133
                }
134
            }
135
        }
136
137
        $lastNotEmpty = $start;
138
139
        for ($i = $start; $i < $phpcsFile->numTokens; $i++) {
140
            if ($i !== $start && isset($endTokens[$tokens[$i]['code']]) === true) {
141
                // Found the end of the statement.
142
                if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS
143
                    || $tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
144
                    || $tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
145
                    || $tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
146
                    || $tokens[$i]['code'] === T_OPEN_TAG
147
                    || $tokens[$i]['code'] === T_CLOSE_TAG
148
                ) {
149
                    return $lastNotEmpty;
150
                }
151
152
                return $i;
153
            }
154
155
            // Skip nested statements.
156
            if (isset($tokens[$i]['scope_closer']) === true
157
                && ($i === $tokens[$i]['scope_opener']
158
                || $i === $tokens[$i]['scope_condition'])
159
            ) {
160
                $i = $tokens[$i]['scope_closer'];
161
            } elseif (isset($tokens[$i]['bracket_closer']) === true
162
                && $i === $tokens[$i]['bracket_opener']
163
            ) {
164
                $i = $tokens[$i]['bracket_closer'];
165
            } elseif (isset($tokens[$i]['parenthesis_closer']) === true
166
                && $i === $tokens[$i]['parenthesis_opener']
167
            ) {
168
                $i = $tokens[$i]['parenthesis_closer'];
169
            }
170
171
            if (isset(\PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === false) {
172
                $lastNotEmpty = $i;
173
            }
174
        }//end for
175
176
        return ($phpcsFile->numTokens - 1);
177
178
    }//end findEndOfStatement()
179
180
181
    /**
182
     * Returns the name of the class that the specified class extends
183
     * (works for classes, anonymous classes and interfaces).
184
     *
185
     * Returns FALSE on error or if there is no extended class name.
186
     *
187
     * {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
188
     * class, but with some improvements which have been introduced in
189
     * PHPCS 2.8.0.
190
     * {@link https://github.com/squizlabs/PHP_CodeSniffer/commit/0011d448119d4c568e3ac1f825ae78815bf2cc34}.
191
     *
192
     * Once the minimum supported PHPCS version for this standard goes beyond
193
     * that, this method can be removed and calls to it replaced with
194
     * `$phpcsFile->findExtendedClassName($stackPtr)` calls.
195
     *
196
     * Last synced with PHPCS version: PHPCS 3.1.0-alpha at commit a9efcc9b0703f3f9f4a900623d4e97128a6aafc6}}
197
     *
198
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
199
     * @param int                   $stackPtr  The position of the class token in the stack.
200
     *
201
     * @return string|false
202
     */
203
    public static function findExtendedClassName(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
204
    {
205
        if (version_compare(self::getVersion(), '3.1.0', '>=') === true) {
206
            return $phpcsFile->findExtendedClassName($stackPtr);
207
        }
208
209
        $tokens = $phpcsFile->getTokens();
210
211
        // Check for the existence of the token.
212
        if (isset($tokens[$stackPtr]) === false) {
213
            return false;
214
        }
215
216 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_CLASS
217
            && $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
218
            && $tokens[$stackPtr]['type'] !== 'T_INTERFACE'
219
        ) {
220
            return false;
221
        }
222
223
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
224
            return false;
225
        }
226
227
        $classCloserIndex = $tokens[$stackPtr]['scope_closer'];
228
        $extendsIndex     = $phpcsFile->findNext(T_EXTENDS, $stackPtr, $classCloserIndex);
229
        if (false === $extendsIndex) {
230
            return false;
231
        }
232
233
        $find = array(
234
            T_NS_SEPARATOR,
235
            T_STRING,
236
            T_WHITESPACE,
237
        );
238
239
        $end  = $phpcsFile->findNext($find, ($extendsIndex + 1), $classCloserIndex, true);
240
        $name = $phpcsFile->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
241
        $name = trim($name);
242
243
        if ($name === '') {
244
            return false;
245
        }
246
247
        return $name;
248
249
    }//end findExtendedClassName()
250
251
252
    /**
253
     * Returns the name(s) of the interface(s) that the specified class implements.
254
     *
255
     * Returns FALSE on error or if there are no implemented interface names.
256
     *
257
     * {@internal Duplicate of same method as introduced in PHPCS 2.7.
258
     * This method also includes an improvement we use which was only introduced
259
     * in PHPCS 2.8.0, so only defer to upstream for higher versions.
260
     * Once the minimum supported PHPCS version for this sniff library goes beyond
261
     * that, this method can be removed and calls to it replaced with
262
     * `$phpcsFile->findImplementedInterfaceNames($stackPtr)` calls.}}
263
     *
264
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
265
     * @param int                   $stackPtr  The position of the class token.
266
     *
267
     * @return array|false
268
     */
269
    public static function findImplementedInterfaceNames(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
270
    {
271
        if (version_compare(self::getVersion(), '2.7.1', '>') === true) {
272
            return $phpcsFile->findImplementedInterfaceNames($stackPtr);
273
        }
274
275
        $tokens = $phpcsFile->getTokens();
276
277
        // Check for the existence of the token.
278
        if (isset($tokens[$stackPtr]) === false) {
279
            return false;
280
        }
281
282
        if ($tokens[$stackPtr]['code'] !== T_CLASS
283
            && $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
284
        ) {
285
            return false;
286
        }
287
288
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
289
            return false;
290
        }
291
292
        $classOpenerIndex = $tokens[$stackPtr]['scope_opener'];
293
        $implementsIndex  = $phpcsFile->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
294
        if ($implementsIndex === false) {
295
            return false;
296
        }
297
298
        $find = array(
299
            T_NS_SEPARATOR,
300
            T_STRING,
301
            T_WHITESPACE,
302
            T_COMMA,
303
        );
304
305
        $end  = $phpcsFile->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
306
        $name = $phpcsFile->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
307
        $name = trim($name);
308
309
        if ($name === '') {
310
            return false;
311
        } else {
312
            $names = explode(',', $name);
313
            $names = array_map('trim', $names);
314
            return $names;
315
        }
316
317
    }//end findImplementedInterfaceNames()
318
319
320
    /**
321
     * Returns the method parameters for the specified function token.
322
     *
323
     * Each parameter is in the following format:
324
     *
325
     * <code>
326
     *   0 => array(
327
     *         'token'             => int,     // The position of the var in the token stack.
328
     *         'name'              => '$var',  // The variable name.
329
     *         'content'           => string,  // The full content of the variable definition.
330
     *         'pass_by_reference' => boolean, // Is the variable passed by reference?
331
     *         'variable_length'   => boolean, // Is the param of variable length through use of `...` ?
332
     *         'type_hint'         => string,  // The type hint for the variable.
333
     *         'nullable_type'     => boolean, // Is the variable using a nullable type?
334
     *        )
335
     * </code>
336
     *
337
     * Parameters with default values have an additional array index of
338
     * 'default' with the value of the default as a string.
339
     *
340
     * {@internal Duplicate of same method as contained in the `\PHP_CodeSniffer_File`
341
     * class, but with some improvements which have been introduced in
342
     * PHPCS 2.8.0.
343
     * {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/1117},
344
     * {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/1193} and
345
     * {@link https://github.com/squizlabs/PHP_CodeSniffer/pull/1293}.
346
     *
347
     * Once the minimum supported PHPCS version for this standard goes beyond
348
     * that, this method can be removed and calls to it replaced with
349
     * `$phpcsFile->getMethodParameters($stackPtr)` calls.
350
     *
351
     * NOTE: This version does not deal with the new T_NULLABLE token type.
352
     * This token is included upstream only in 2.8.0+ and as we defer to upstream
353
     * in that case, no need to deal with it here.
354
     *
355
     * Last synced with PHPCS version: PHPCS 2.9.0-alpha at commit f1511adad043edfd6d2e595e77385c32577eb2bc}}
356
     *
357
     * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
358
     * @param int                   $stackPtr  The position in the stack of the
359
     *                                         function token to acquire the
360
     *                                         parameters for.
361
     *
362
     * @return array|false
363
     * @throws \PHP_CodeSniffer_Exception If the specified $stackPtr is not of
364
     *                                    type T_FUNCTION or T_CLOSURE.
365
     */
366
    public static function getMethodParameters(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
367
    {
368
        if (version_compare(self::getVersion(), '2.7.1', '>') === true) {
369
            return $phpcsFile->getMethodParameters($stackPtr);
370
        }
371
372
        $tokens = $phpcsFile->getTokens();
373
374
        // Check for the existence of the token.
375
        if (isset($tokens[$stackPtr]) === false) {
376
            return false;
377
        }
378
379 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_FUNCTION && $tokens[$stackPtr]['code'] !== T_CLOSURE) {
380
            throw new \PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
381
        }
382
383
        $opener = $tokens[$stackPtr]['parenthesis_opener'];
384
        $closer = $tokens[$stackPtr]['parenthesis_closer'];
385
386
        $vars            = array();
387
        $currVar         = null;
388
        $paramStart      = ($opener + 1);
389
        $defaultStart    = null;
390
        $paramCount      = 0;
391
        $passByReference = false;
392
        $variableLength  = false;
393
        $typeHint        = '';
394
        $nullableType    = false;
395
396
        for ($i = $paramStart; $i <= $closer; $i++) {
397
            // Check to see if this token has a parenthesis or bracket opener. If it does
398
            // it's likely to be an array which might have arguments in it. This
399
            // could cause problems in our parsing below, so lets just skip to the
400
            // end of it.
401 View Code Duplication
            if (isset($tokens[$i]['parenthesis_opener']) === true) {
402
                // Don't do this if it's the close parenthesis for the method.
403
                if ($i !== $tokens[$i]['parenthesis_closer']) {
404
                    $i = ($tokens[$i]['parenthesis_closer'] + 1);
405
                }
406
            }
407
408 View Code Duplication
            if (isset($tokens[$i]['bracket_opener']) === true) {
409
                // Don't do this if it's the close parenthesis for the method.
410
                if ($i !== $tokens[$i]['bracket_closer']) {
411
                    $i = ($tokens[$i]['bracket_closer'] + 1);
412
                }
413
            }
414
415
            switch ($tokens[$i]['type']) {
416
                case 'T_BITWISE_AND':
417
                    $passByReference = true;
418
                    break;
419
                case 'T_VARIABLE':
420
                    $currVar = $i;
421
                    break;
422
                case 'T_ELLIPSIS':
423
                    $variableLength = true;
424
                    break;
425
                case 'T_ARRAY_HINT':
426
                case 'T_CALLABLE':
427
                    $typeHint .= $tokens[$i]['content'];
428
                    break;
429
                case 'T_SELF':
430
                case 'T_PARENT':
431
                case 'T_STATIC':
432
                    // Self and parent are valid, static invalid, but was probably intended as type hint.
433
                    if (isset($defaultStart) === false) {
434
                        $typeHint .= $tokens[$i]['content'];
435
                    }
436
                    break;
437
                case 'T_STRING':
438
                    // This is a string, so it may be a type hint, but it could
439
                    // also be a constant used as a default value.
440
                    $prevComma = false;
441 View Code Duplication
                    for ($t = $i; $t >= $opener; $t--) {
442
                        if ($tokens[$t]['code'] === T_COMMA) {
443
                            $prevComma = $t;
444
                            break;
445
                        }
446
                    }
447
448
                    if ($prevComma !== false) {
449
                        $nextEquals = false;
450 View Code Duplication
                        for ($t = $prevComma; $t < $i; $t++) {
451
                            if ($tokens[$t]['code'] === T_EQUAL) {
452
                                $nextEquals = $t;
453
                                break;
454
                            }
455
                        }
456
457
                        if ($nextEquals !== false) {
458
                            break;
459
                        }
460
                    }
461
462
                    if ($defaultStart === null) {
463
                        $typeHint .= $tokens[$i]['content'];
464
                    }
465
                    break;
466
                case 'T_NS_SEPARATOR':
467
                    // Part of a type hint or default value.
468
                    if ($defaultStart === null) {
469
                        $typeHint .= $tokens[$i]['content'];
470
                    }
471
                    break;
472
                case 'T_INLINE_THEN':
473
                    if ($defaultStart === null) {
474
                        $nullableType = true;
475
                        $typeHint    .= $tokens[$i]['content'];
476
                    }
477
                    break;
478
                case 'T_CLOSE_PARENTHESIS':
479
                case 'T_COMMA':
480
                    // If it's null, then there must be no parameters for this
481
                    // method.
482
                    if ($currVar === null) {
483
                        continue;
484
                    }
485
486
                    $vars[$paramCount]            = array();
487
                    $vars[$paramCount]['token']   = $currVar;
488
                    $vars[$paramCount]['name']    = $tokens[$currVar]['content'];
489
                    $vars[$paramCount]['content'] = trim($phpcsFile->getTokensAsString($paramStart, ($i - $paramStart)));
490
491
                    if ($defaultStart !== null) {
492
                        $vars[$paramCount]['default'] = trim(
493
                            $phpcsFile->getTokensAsString(
494
                                $defaultStart,
495
                                ($i - $defaultStart)
496
                            )
497
                        );
498
                    }
499
500
                    $vars[$paramCount]['pass_by_reference'] = $passByReference;
501
                    $vars[$paramCount]['variable_length']   = $variableLength;
502
                    $vars[$paramCount]['type_hint']         = $typeHint;
503
                    $vars[$paramCount]['nullable_type']     = $nullableType;
504
505
                    // Reset the vars, as we are about to process the next parameter.
506
                    $defaultStart    = null;
507
                    $paramStart      = ($i + 1);
508
                    $passByReference = false;
509
                    $variableLength  = false;
510
                    $typeHint        = '';
511
                    $nullableType    = false;
512
513
                    $paramCount++;
514
                    break;
515
                case 'T_EQUAL':
516
                    $defaultStart = ($i + 1);
517
                    break;
518
            }//end switch
519
        }//end for
520
521
        return $vars;
522
523
    }//end getMethodParameters()
524
}
525