Failed Conditions
Branch test-scrutinizer-coverage (7e25b2)
by Wim
13:51 queued 10:07
created

PHPCompatibility_Sniffs_PHP_NewClosureSniff   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 86.44%

Importance

Changes 0
Metric Value
wmc 20
lcom 1
cbo 3
dl 0
loc 163
ccs 51
cts 59
cp 0.8644
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 5 1
A isClosureStatic() 0 7 2
C process() 0 82 11
B findThisUsageInClosure() 0 27 6
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
/**
13
 * PHPCompatibility_Sniffs_PHP_NewClosure.
14
 *
15
 * Closures are available since PHP 5.3
16
 *
17
 * PHP version 5.3
18
 *
19
 * @category PHP
20
 * @package  PHPCompatibility
21
 * @author   Wim Godden <[email protected]>
22
 */
23
class PHPCompatibility_Sniffs_PHP_NewClosureSniff extends PHPCompatibility_Sniff
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
24
{
25
    /**
26
     * Returns an array of tokens this test wants to listen for.
27
     *
28
     * @return array
29
     */
30 24
    public function register()
31
    {
32 24
        return array(T_CLOSURE);
33
34
    }//end register()
35
36
    /**
37
     * Processes this test, when one of its tokens is encountered.
38
     *
39
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
40
     * @param int                  $stackPtr  The position of the current token
41
     *                                        in the stack passed in $tokens.
42
     *
43
     * @return void
44
     */
45 2
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
46
    {
47 2
        $tokens = $phpcsFile->getTokens();
0 ignored issues
show
Unused Code introduced by
$tokens is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
48
49 2
        if ($this->supportsBelow('5.2')) {
50 1
            $phpcsFile->addError(
51 1
                'Closures / anonymous functions are not available in PHP 5.2 or earlier',
52
                $stackPtr,
53 1
                'Found'
54
            );
55
        }
56
57 2
        $isStatic = $this->isClosureStatic($phpcsFile, $stackPtr);
58 2
        $usesThis = $this->findThisUsageInClosure($phpcsFile, $stackPtr);
59
60 2
        if ($this->supportsBelow('5.3')) {
61
62
            /*
63
             * Closures can only be declared as static since PHP 5.4.
64
             */
65 2
            if ($isStatic === true) {
66 2
                $phpcsFile->addError(
67 2
                    'Closures / anonymous functions could not be declared as static in PHP 5.3 or earlier',
68
                    $stackPtr,
69 2
                    'StaticFound'
70
                );
71
            }
72
73
            /*
74
             * Closures declared within classes only have access to $this since PHP 5.4.
75
             */
76 2
            if ($usesThis !== false) {
77 2
                $thisFound = $usesThis;
78
                do {
79 2
                    $phpcsFile->addError(
80 2
                        'Closures / anonymous functions did not have access to $this in PHP 5.3 or earlier',
81
                        $thisFound,
82 2
                        'ThisFound'
83
                    );
84
85 2
                    $thisFound = $this->findThisUsageInClosure($phpcsFile, $stackPtr, ($thisFound + 1));
86
87 2
                } while ($thisFound !== false);
88
            }
89
        }
90
91
        /*
92
         * Check for correct usage.
93
         */
94 2
        if ($this->supportsAbove('5.4') && $usesThis !== false) {
95
96 1
            $thisFound = $usesThis;
97
98
            do {
99
                /*
100
                 * Closures only have access to $this if not declared as static.
101
                 */
102 1
                if ($isStatic === true) {
103 1
                    $phpcsFile->addError(
104 1
                        'Closures / anonymous functions declared as static do not have access to $this',
105
                        $thisFound,
106 1
                        'ThisFoundInStatic'
107
                    );
108
                }
109
110
                /*
111
                 * Closures only have access to $this if used within a class context.
112
                 */
113 1
                elseif ($this->inClassScope($phpcsFile, $stackPtr, false) === false) {
114 1
                    $phpcsFile->addError(
115 1
                        'Closures / anonymous functions only have access to $this if used within a class',
116
                        $thisFound,
117 1
                        'ThisFoundOutsideClass'
118
                    );
119
                }
120
121 1
                $thisFound = $this->findThisUsageInClosure($phpcsFile, $stackPtr, ($thisFound + 1));
122
123 1
            } while ($thisFound !== false);
124
        }
125
126 2
    }//end process()
127
128
129
    /**
130
     * Check whether the closure is declared as static.
131
     *
132
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
133
     * @param int                  $stackPtr  The position of the current token
134
     *                                        in the stack passed in $tokens.
135
     *
136
     * @return bool
137
     */
138 2
    protected function isClosureStatic(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
139
    {
140 2
        $tokens    = $phpcsFile->getTokens();
141 2
        $prevToken = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
142
143 2
        return ($prevToken !== false && $tokens[$prevToken]['code'] === T_STATIC);
144
    }
145
146
147
    /**
148
     * Check if the code within a closure uses the $this variable.
149
     *
150
     * @param PHP_CodeSniffer_File $phpcsFile  The file being scanned.
151
     * @param int                  $stackPtr   The position of the closure token.
152
     * @param int                  $startToken Optional. The position within the closure to continue searching from.
153
     *
154
     * @return int|false The stackPtr to the first $this usage if found or false if
155
     *                   $this is not used or usage of $this could not reliably be determined.
156
     */
157 2
    protected function findThisUsageInClosure(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $startToken = null)
158
    {
159 2
        $tokens = $phpcsFile->getTokens();
160
161 2
        if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
162
            // Live coding or parse error.
163
            return false;
164
        }
165
166
        // Make sure the optional $startToken is valid.
167 2
        if (isset($startToken) === true && (isset($tokens[$startToken]) === false || $startToken >= $tokens[$stackPtr]['scope_closer'])) {
168
            return false;
169
        }
170
171 2
        $start = ($tokens[$stackPtr]['scope_opener'] + 1);
172 2
        if (isset($startToken) === true) {
173 2
            $start = $startToken;
174
        }
175
176 2
        return $phpcsFile->findNext(
177 2
            T_VARIABLE,
178
            $start,
179 2
            $tokens[$stackPtr]['scope_closer'],
180 2
            false,
181 2
            '$this'
182
        );
183
    }
184
185
}//end class
186