Completed
Push — master ( 6a4ee6...24bb48 )
by Juliette
01:33
created

PHP/ForbiddenClosureUseVariableNamesSniff.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * PHP 7.1 Forbidden variable names in closure use statements.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @category PHP
8
 * @package  PHPCompatibility
9
 * @author   Juliette Reinders Folmer <[email protected]>
10
 */
11
12
/**
13
 * PHP 7.1 Forbidden variable names in closure use statements.
14
 *
15
 * Variables bound to a closure via the use construct cannot use the same name
16
 * as any superglobals, $this, or any parameter since PHP 7.1.
17
 *
18
 * PHP version 7.1
19
 *
20
 * @category PHP
21
 * @package  PHPCompatibility
22
 * @author   Juliette Reinders Folmer <[email protected]>
23
 */
24
class PHPCompatibility_Sniffs_PHP_ForbiddenClosureUseVariableNamesSniff extends PHPCompatibility_Sniff
25
{
26
27
    /**
28
     * Returns an array of tokens this test wants to listen for.
29
     *
30
     * @return array
31
     */
32
    public function register()
33
    {
34
        return array(T_USE);
35
36
    }//end register()
37
38
    /**
39
     * Processes this test, when one of its tokens is encountered.
40
     *
41
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
42
     * @param int                  $stackPtr  The position of the current token
43
     *                                        in the stack passed in $tokens.
44
     *
45
     * @return void
46
     */
47
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
48
    {
49
        if ($this->supportsAbove('7.1') === false) {
50
            return;
51
        }
52
53
        $tokens = $phpcsFile->getTokens();
54
55
        // Verify this use statement is used with a closure - if so, it has to have parenthesis before it.
56
        $previousNonEmpty = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
57
        if ($previousNonEmpty === false || $tokens[$previousNonEmpty]['code'] !== T_CLOSE_PARENTHESIS
58
            || isset($tokens[$previousNonEmpty]['parenthesis_opener']) === false
59
        ) {
60
            return;
61
        }
62
63
        // ... and (a variable within) parenthesis after it.
64
        $nextNonEmpty = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
65
        if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== T_OPEN_PARENTHESIS) {
66
            return;
67
        }
68
69
        if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
70
            // Live coding.
71
            return;
72
        }
73
74
        $closurePtr = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($tokens[$previousNonEmpty]['parenthesis_opener'] - 1), null, true);
75
        if ($closurePtr === false || $tokens[$closurePtr]['code'] !== T_CLOSURE) {
76
            return;
77
        }
78
79
        // Get the parameters declared by the closure.
80
        $closureParams = $this->getMethodParameters($phpcsFile, $closurePtr);
0 ignored issues
show
It seems like $closurePtr defined by $phpcsFile->findPrevious...ener'] - 1, null, true) on line 74 can also be of type boolean; however, PHPCompatibility_Sniff::getMethodParameters() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
81
82
        $errorMsg = 'Variables bound to a closure via the use construct cannot use the same name as superglobals, $this, or a declared parameter since PHP 7.1. Found: %s';
83
84
        for ($i = ($nextNonEmpty + 1); $i < $tokens[$nextNonEmpty]['parenthesis_closer']; $i++) {
85
            if ($tokens[$i]['code'] !== T_VARIABLE) {
86
                continue;
87
            }
88
89
            $variableName = $tokens[$i]['content'];
90
91
            if ($variableName === '$this') {
92
                $phpcsFile->addError($errorMsg, $i, 'FoundThis', array($variableName));
93
                continue;
94
            }
95
96
            if (in_array($variableName, $this->superglobals, true) === true) {
97
                $phpcsFile->addError($errorMsg, $i, 'FoundSuperglobal', array($variableName));
98
                continue;
99
            }
100
101
            // Check whether it is one of the parameters declared by the closure.
102
            if (empty($closureParams) === false) {
103
                foreach ($closureParams as $param) {
104
                    if ($param['name'] === $variableName) {
105
                        $phpcsFile->addError($errorMsg, $i, 'FoundShadowParam', array($variableName));
106
                        continue 2;
107
                    }
108
                }
109
            }
110
        }
111
112
    }//end process()
113
114
}//end class
115