Completed
Push — feature/668-self-in-closures ( 295b9f )
by Juliette
02:11
created

NewClosureSniff::findThisUsageInClosure()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
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
        /*
60
         * Closures can only be declared as static since PHP 5.4.
61
         */
62
        $isStatic = $this->isClosureStatic($phpcsFile, $stackPtr);
63
        if ($this->supportsBelow('5.3') && $isStatic === true) {
64
            $phpcsFile->addError(
65
                'Closures / anonymous functions could not be declared as static in PHP 5.3 or earlier',
66
                $stackPtr,
67
                'StaticFound'
68
            );
69
        }
70
71
        $tokens = $phpcsFile->getTokens();
72
73
        if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
74
            // Live coding or parse error.
75
            return;
76
        }
77
78
        $scopeStart = ($tokens[$stackPtr]['scope_opener'] + 1);
79
        $scopeEnd   = $tokens[$stackPtr]['scope_closer'];
80
        $usesThis   = $this->findThisUsageInClosure($phpcsFile, $scopeStart, $scopeEnd);
81
82
        if ($this->supportsBelow('5.3')) {
83
            /*
84
             * Closures declared within classes only have access to $this since PHP 5.4.
85
             */
86
            if ($usesThis !== false) {
87
                $thisFound = $usesThis;
88
                do {
89
                    $phpcsFile->addError(
90
                        'Closures / anonymous functions did not have access to $this in PHP 5.3 or earlier',
91
                        $thisFound,
92
                        'ThisFound'
93
                    );
94
95
                    $thisFound = $this->findThisUsageInClosure($phpcsFile, ($thisFound + 1), $scopeEnd);
96
97
                } while ($thisFound !== false);
98
            }
99
100
            /*
101
             * Closures declared within classes only have access to self/parent/static since PHP 5.4.
102
             */
103
            $usesClassRef = $this->findClassRefUsageInClosure($phpcsFile, $scopeStart, $scopeEnd);
104
105
            if ($usesClassRef !== false) {
106
                do {
107
                    $phpcsFile->addError(
108
                        'Closures / anonymous functions could not use "%s::" in PHP 5.3 or earlier',
109
                        $usesClassRef,
110
                        'ClassRefFound',
111
                        array(strtolower($tokens[$usesClassRef]['content']))
112
                    );
113
114
                    $usesClassRef = $this->findClassRefUsageInClosure($phpcsFile, ($usesClassRef + 1), $scopeEnd);
115
116
                } while ($usesClassRef !== false);
117
            }
118
        }
119
120
        /*
121
         * Check for correct usage.
122
         */
123
        if ($this->supportsAbove('5.4') && $usesThis !== false) {
124
125
            $thisFound = $usesThis;
126
127
            do {
128
                /*
129
                 * Closures only have access to $this if not declared as static.
130
                 */
131
                if ($isStatic === true) {
132
                    $phpcsFile->addError(
133
                        'Closures / anonymous functions declared as static do not have access to $this',
134
                        $thisFound,
135
                        'ThisFoundInStatic'
136
                    );
137
                }
138
139
                /*
140
                 * Closures only have access to $this if used within a class context.
141
                 */
142
                elseif ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
143
                    $phpcsFile->addWarning(
144
                        'Closures / anonymous functions only have access to $this if used within a class or when bound to an object using bindTo(). Please verify.',
145
                        $thisFound,
146
                        'ThisFoundOutsideClass'
147
                    );
148
                }
149
150
                $thisFound = $this->findThisUsageInClosure($phpcsFile, ($thisFound + 1), $scopeEnd);
151
152
            } while ($thisFound !== false);
153
        }
154
155
        // Prevent double reporting for nested closures.
156
        return $scopeEnd;
157
158
    }//end process()
159
160
161
    /**
162
     * Check whether the closure is declared as static.
163
     *
164
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
165
     * @param int                   $stackPtr  The position of the current token
166
     *                                         in the stack passed in $tokens.
167
     *
168
     * @return bool
169
     */
170
    protected function isClosureStatic(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
171
    {
172
        $tokens    = $phpcsFile->getTokens();
173
        $prevToken = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
174
175
        return ($prevToken !== false && $tokens[$prevToken]['code'] === T_STATIC);
176
    }
177
178
179
    /**
180
     * Check if the code within a closure uses the $this variable.
181
     *
182
     * @param \PHP_CodeSniffer_File $phpcsFile  The file being scanned.
183
     * @param int                   $startToken The position within the closure to continue searching from.
184
     * @param int                   $endToken   The closure scope closer to stop searching at.
185
     *
186
     * @return int|false The stackPtr to the first $this usage if found or false if
187
     *                   $this is not used.
188
     */
189
    protected function findThisUsageInClosure(\PHP_CodeSniffer_File $phpcsFile, $startToken, $endToken)
190
    {
191
        // Make sure the $startToken is valid.
192
        if ($startToken >= $endToken) {
193
            return false;
194
        }
195
196
        return $phpcsFile->findNext(
197
            T_VARIABLE,
198
            $startToken,
199
            $endToken,
200
            false,
201
            '$this'
202
        );
203
    }
204
205
    /**
206
     * Check if the code within a closure uses "self/parent/static".
207
     *
208
     * @param \PHP_CodeSniffer_File $phpcsFile  The file being scanned.
209
     * @param int                   $startToken The position within the closure to continue searching from.
210
     * @param int                   $endToken   The closure scope closer to stop searching at.
211
     *
212
     * @return int|false The stackPtr to the first classRef usage if found or false if
213
     *                   they are not used.
214
     */
215
    protected function findClassRefUsageInClosure(\PHP_CodeSniffer_File $phpcsFile, $startToken, $endToken)
216
    {
217
        // Make sure the $startToken is valid.
218
        if ($startToken >= $endToken) {
219
            return false;
220
        }
221
222
        $tokens   = $phpcsFile->getTokens();
223
        $classRef = $phpcsFile->findNext(array(T_SELF, T_PARENT, T_STATIC), $startToken, $endToken);
224
225
        if ($classRef === false || $tokens[$classRef]['code'] !== T_STATIC) {
226
            return $classRef;
227
        }
228
229
        // T_STATIC, make sure it is used as a class reference.
230
        $next = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($classRef + 1), $endToken, true);
231 View Code Duplication
        if ($next === false || $tokens[$next]['code'] !== T_DOUBLE_COLON) {
232
            return false;
233
        }
234
235
        return $classRef;
236
    }
237
238
}//end class
239