Completed
Push — php-7.1/252-new-keyed-lists-sn... ( 91aee0 )
by Juliette
03:50 queued 01:50
created

NewKeyedListSniff::bowOutEarly()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\Lists\NewKeyedListSniff.
4
 *
5
 * PHP version 7.1
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\NewKeyedListSniff.
19
 *
20
 * "You can now specify keys in list(), or its new shorthand [] syntax. "
21
 *
22
 * PHP version 7.1
23
 *
24
 * @category PHP
25
 * @package  PHPCompatibility\Lists
26
 * @author   Juliette Reinders Folmer <[email protected]>
27
 */
28
class NewKeyedListSniff 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
     * Do a version check to determine if this sniff needs to run at all.
46
     *
47
     * @return bool
48
     */
49
    protected function bowOutEarly()
50
    {
51
        return ($this->supportsBelow('7.0') === false);
52
    }
53
54
    /**
55
     * Processes this test, when one of its tokens is encountered.
56
     *
57
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
58
     * @param int                   $stackPtr  The position of the current token in the
59
     *                                         stack passed in $tokens.
60
     *
61
     * @return void
62
     */
63
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
64
    {
65
        if ($this->bowOutEarly() === true) {
66
            return;
67
        }
68
69
        $tokens = $phpcsFile->getTokens();
70
71
        if ($tokens[$stackPtr]['code'] === T_OPEN_SHORT_ARRAY
72
            && $this->isShortList($phpcsFile, $stackPtr) === false
73
        ) {
74
            // Short array, not short list.
75
            return;
76
        }
77
78
        if ($tokens[$stackPtr]['code'] === T_LIST) {
79
            $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
80 View Code Duplication
            if ($nextNonEmpty === false
81
                || $tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS
82
                || isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false
83
            ) {
84
                // Parse error or live coding.
85
                return;
86
            }
87
88
            $opener = $nextNonEmpty;
89
            $closer = $tokens[$nextNonEmpty]['parenthesis_closer'];
90
        } else {
91
            // Short list syntax.
92
            $opener = $stackPtr;
93
94
            if (isset($tokens[$stackPtr]['bracket_closer'])) {
95
                $closer = $tokens[$stackPtr]['bracket_closer'];
96
            }
97
        }
98
99
        if (isset($opener, $closer) === false) {
100
            return;
101
        }
102
103
        $this->examineList($phpcsFile, $opener, $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...
104
    }
105
106
107
    /**
108
     * Examine the contents of a list construct to determine whether an error needs to be thrown.
109
     *
110
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
111
     * @param int                   $opener    The position of the list open token.
112
     * @param int                   $closer    The position of the list close token.
113
     *
114
     * @return void
115
     */
116
    protected function examineList(\PHP_CodeSniffer_File $phpcsFile, $opener, $closer)
117
    {
118
        $targets = array(T_DOUBLE_ARROW);
119
        $start   = $opener;
120
        while (($start = $this->hasTargetInList($phpcsFile, $start, $closer, $targets)) !== false) {
121
            $phpcsFile->addError(
122
                'Specifying keys in list constructs is not supported in PHP 7.0 or earlier.',
123
                $start,
124
                'Found'
125
            );
126
        }
127
    }
128
129
130
    /**
131
     * Check whether a certain target token exists within a list construct.
132
     *
133
     * Skips past nested list constructs, so these can be examined based on their own token.
134
     *
135
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
136
     * @param int                   $start     The position of the list open token or a token
137
     *                                         within the list to start (resume) the examination from.
138
     * @param int                   $closer    The position of the list close token.
139
     * @param array                 $targets   An array with one or more token constants to look for.
140
     *
141
     * @return int|bool Stack pointer to the target token if encountered. False otherwise.
142
     */
143
    protected function hasTargetInList(\PHP_CodeSniffer_File $phpcsFile, $start, $closer, $targets)
144
    {
145
        $tokens     = $phpcsFile->getTokens();
146
        $targets    = array_flip($targets);
147
        $allTargets = $targets + array_flip($this->register());
148
149
        for ($i = ($start + 1); $i < $closer; $i++) {
150
            if (isset($allTargets[$tokens[$i]['code']]) === false) {
151
                continue;
152
            }
153
154
            if (isset($targets[$tokens[$i]['code']]) === true) {
155
                return $i;
156
            }
157
158
            // Skip past nested list constructs.
159
            if ($tokens[$i]['code'] === T_LIST) {
160
                $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true);
161
                if ($nextNonEmpty !== false
162
                    && $tokens[$nextNonEmpty]['code'] === T_OPEN_PARENTHESIS
163
                    && isset($tokens[$nextNonEmpty]['parenthesis_closer']) === true
164
                ) {
165
                    $i = $tokens[$nextNonEmpty]['parenthesis_closer'];
166
                }
167 View Code Duplication
            } elseif ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY
168
                && isset($tokens[$i]['bracket_closer'])
169
            ) {
170
                $i = $tokens[$i]['bracket_closer'];
171
            }
172
        }
173
174
        return false;
175
    }
176
}
177