Completed
Push — php-5.3/649-new-array_reduce-i... ( 1c76df...974b4f )
by Juliette
06:24
created

NewClosureSniff::findThisUsageInClosure()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 27
Code Lines 15

Duplication

Lines 4
Ratio 14.81 %

Importance

Changes 0
Metric Value
dl 4
loc 27
rs 8.439
c 0
b 0
f 0
cc 6
eloc 15
nc 4
nop 3
1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\PHP\NewClosure.
4
 *
5
 * PHP version 5.3
6
 *
7
 * @category PHP
8
 * @package  PHPCompatibility
9
 * @author   Wim Godden <[email protected]>
10
 */
11
12
namespace PHPCompatibility\Sniffs\PHP;
13
14
use PHPCompatibility\Sniff;
15
16
/**
17
 * \PHPCompatibility\Sniffs\PHP\NewClosure.
18
 *
19
 * Closures are available since PHP 5.3
20
 *
21
 * PHP version 5.3
22
 *
23
 * @category PHP
24
 * @package  PHPCompatibility
25
 * @author   Wim Godden <[email protected]>
26
 */
27
class NewClosureSniff extends Sniff
28
{
29
    /**
30
     * Returns an array of tokens this test wants to listen for.
31
     *
32
     * @return array
33
     */
34
    public function register()
35
    {
36
        return array(T_CLOSURE);
37
38
    }//end register()
39
40
    /**
41
     * Processes this test, when one of its tokens is encountered.
42
     *
43
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
44
     * @param int                   $stackPtr  The position of the current token
45
     *                                         in the stack passed in $tokens.
46
     *
47
     * @return void
48
     */
49
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
50
    {
51
        if ($this->supportsBelow('5.2')) {
52
            $phpcsFile->addError(
53
                'Closures / anonymous functions are not available in PHP 5.2 or earlier',
54
                $stackPtr,
55
                'Found'
56
            );
57
        }
58
59
        $isStatic = $this->isClosureStatic($phpcsFile, $stackPtr);
60
        $usesThis = $this->findThisUsageInClosure($phpcsFile, $stackPtr);
61
62
        if ($this->supportsBelow('5.3')) {
63
            /*
64
             * Closures can only be declared as static since PHP 5.4.
65
             */
66
            if ($isStatic === true) {
67
                $phpcsFile->addError(
68
                    'Closures / anonymous functions could not be declared as static in PHP 5.3 or earlier',
69
                    $stackPtr,
70
                    'StaticFound'
71
                );
72
            }
73
74
            /*
75
             * Closures declared within classes only have access to $this since PHP 5.4.
76
             */
77
            if ($usesThis !== false) {
78
                $thisFound = $usesThis;
79
                do {
80
                    $phpcsFile->addError(
81
                        'Closures / anonymous functions did not have access to $this in PHP 5.3 or earlier',
82
                        $thisFound,
83
                        'ThisFound'
84
                    );
85
86
                    $thisFound = $this->findThisUsageInClosure($phpcsFile, $stackPtr, ($thisFound + 1));
87
88
                } while ($thisFound !== false);
89
            }
90
        }
91
92
        /*
93
         * Check for correct usage.
94
         */
95
        if ($this->supportsAbove('5.4') && $usesThis !== false) {
96
97
            $thisFound = $usesThis;
98
99
            do {
100
                /*
101
                 * Closures only have access to $this if not declared as static.
102
                 */
103
                if ($isStatic === true) {
104
                    $phpcsFile->addError(
105
                        'Closures / anonymous functions declared as static do not have access to $this',
106
                        $thisFound,
107
                        'ThisFoundInStatic'
108
                    );
109
                }
110
111
                /*
112
                 * Closures only have access to $this if used within a class context.
113
                 */
114
                elseif ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
115
                    $phpcsFile->addWarning(
116
                        'Closures / anonymous functions only have access to $this if used within a class or when bound to an object using bindTo(). Please verify.',
117
                        $thisFound,
118
                        'ThisFoundOutsideClass'
119
                    );
120
                }
121
122
                $thisFound = $this->findThisUsageInClosure($phpcsFile, $stackPtr, ($thisFound + 1));
123
124
            } while ($thisFound !== false);
125
        }
126
127
    }//end process()
128
129
130
    /**
131
     * Check whether the closure is declared as static.
132
     *
133
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
134
     * @param int                   $stackPtr  The position of the current token
135
     *                                         in the stack passed in $tokens.
136
     *
137
     * @return bool
138
     */
139
    protected function isClosureStatic(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
140
    {
141
        $tokens    = $phpcsFile->getTokens();
142
        $prevToken = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
143
144
        return ($prevToken !== false && $tokens[$prevToken]['code'] === T_STATIC);
145
    }
146
147
148
    /**
149
     * Check if the code within a closure uses the $this variable.
150
     *
151
     * @param \PHP_CodeSniffer_File $phpcsFile  The file being scanned.
152
     * @param int                   $stackPtr   The position of the closure token.
153
     * @param int                   $startToken Optional. The position within the closure to continue searching from.
154
     *
155
     * @return int|false The stackPtr to the first $this usage if found or false if
156
     *                   $this is not used or usage of $this could not reliably be determined.
157
     */
158
    protected function findThisUsageInClosure(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $startToken = null)
159
    {
160
        $tokens = $phpcsFile->getTokens();
161
162 View Code Duplication
        if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
163
            // Live coding or parse error.
164
            return false;
165
        }
166
167
        // Make sure the optional $startToken is valid.
168
        if (isset($startToken) === true && (isset($tokens[$startToken]) === false || $startToken >= $tokens[$stackPtr]['scope_closer'])) {
169
            return false;
170
        }
171
172
        $start = ($tokens[$stackPtr]['scope_opener'] + 1);
173
        if (isset($startToken) === true) {
174
            $start = $startToken;
175
        }
176
177
        return $phpcsFile->findNext(
178
            T_VARIABLE,
179
            $start,
180
            $tokens[$stackPtr]['scope_closer'],
181
            false,
182
            '$this'
183
        );
184
    }
185
186
}//end class
187