Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 82 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
StaticClosureSniff | |
0.00% |
0 / 82 |
|
0.00% |
0 / 3 |
1190 | |
0.00% |
0 / 1 |
register | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
process | |
0.00% |
0 / 74 |
|
0.00% |
0 / 1 |
756 | |||
isStaticClassProperty | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | |
3 | /** |
4 | * Use static closure when the inner body does not use $this |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License along |
17 | * with this program; if not, write to the Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | * http://www.gnu.org/copyleft/gpl.html |
20 | * |
21 | * @file |
22 | */ |
23 | |
24 | namespace MediaWiki\Sniffs\Usage; |
25 | |
26 | use PHP_CodeSniffer\Files\File; |
27 | use PHP_CodeSniffer\Sniffs\Sniff; |
28 | use PHP_CodeSniffer\Util\Tokens; |
29 | |
30 | class StaticClosureSniff implements Sniff { |
31 | |
32 | /** |
33 | * @inheritDoc |
34 | */ |
35 | public function register(): array { |
36 | return [ T_CLOSURE ]; |
37 | } |
38 | |
39 | /** |
40 | * @param File $phpcsFile |
41 | * @param int $stackPtr The current token index. |
42 | */ |
43 | public function process( File $phpcsFile, $stackPtr ) { |
44 | $prevClosureToken = $phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true ); |
45 | if ( $prevClosureToken === false ) { |
46 | return; |
47 | } |
48 | |
49 | $tokens = $phpcsFile->getTokens(); |
50 | $containsNonStaticStatements = false; |
51 | $unclearSituation = false; |
52 | |
53 | $searchToken = [ |
54 | T_VARIABLE, |
55 | T_DOUBLE_QUOTED_STRING, |
56 | T_HEREDOC, |
57 | T_PARENT, |
58 | T_SELF, |
59 | T_STATIC, |
60 | T_CLOSURE, |
61 | T_ANON_CLASS, |
62 | ]; |
63 | $end = $tokens[$stackPtr]['scope_closer']; |
64 | |
65 | // Search for tokens which indicates that this cannot be a static closure |
66 | $next = $phpcsFile->findNext( $searchToken, $tokens[$stackPtr]['scope_opener'] + 1, $end ); |
67 | while ( $next !== false ) { |
68 | $code = $tokens[$next]['code']; |
69 | switch ( $code ) { |
70 | case T_VARIABLE: |
71 | if ( $tokens[$next]['content'] === '$this' ) { |
72 | $containsNonStaticStatements = true; |
73 | } |
74 | break; |
75 | |
76 | case T_DOUBLE_QUOTED_STRING: |
77 | case T_HEREDOC: |
78 | if ( preg_match( '/\${?this\b/', $tokens[$next]['content'] ) ) { |
79 | $containsNonStaticStatements = true; |
80 | } |
81 | break; |
82 | |
83 | case T_PARENT: |
84 | case T_SELF: |
85 | case T_STATIC: |
86 | // Use of consts are allowed in static closures |
87 | $nextToken = $phpcsFile->findNext( Tokens::$emptyTokens, $next + 1, $end, true ); |
88 | // In case of T_STATIC ignore the static keyword on closures |
89 | if ( $nextToken !== false |
90 | && $tokens[$nextToken]['code'] !== T_CLOSURE |
91 | && !$this->isStaticClassProperty( $phpcsFile, $tokens, $nextToken, $end ) |
92 | ) { |
93 | $prevToken = $phpcsFile->findPrevious( Tokens::$emptyTokens, $next - 1, null, true ); |
94 | // Okay on "new self" |
95 | if ( $prevToken === false || $tokens[$prevToken]['code'] !== T_NEW ) { |
96 | // php allows to call non-static method with self:: or parent:: or static:: |
97 | // That is normally a static call, but keep it as is, because it is unclear |
98 | // and can break when changing. |
99 | // Also keep unknown token sequences or unclear syntax |
100 | $unclearSituation = true; |
101 | } |
102 | } |
103 | if ( $nextToken !== false ) { |
104 | // Skip over analyzed tokens |
105 | $next = $nextToken; |
106 | } |
107 | break; |
108 | |
109 | case T_CLOSURE: |
110 | // Skip arguments and use parameter for closure, which can contains T_SELF as type hint |
111 | // But search also inside nested closures for $this |
112 | if ( isset( $tokens[$next]['scope_opener'] ) ) { |
113 | $next = $tokens[$next]['scope_opener']; |
114 | } |
115 | break; |
116 | |
117 | case T_ANON_CLASS: |
118 | if ( isset( $tokens[$next]['scope_closer'] ) ) { |
119 | // Skip to the end of the anon class because $this in anon is not relevant for this sniff |
120 | $next = $tokens[$next]['scope_closer']; |
121 | } |
122 | break; |
123 | } |
124 | $next = $phpcsFile->findNext( $searchToken, $next + 1, $end ); |
125 | } |
126 | |
127 | if ( $unclearSituation ) { |
128 | // Keep everything as is |
129 | return; |
130 | } |
131 | |
132 | if ( $tokens[$prevClosureToken]['code'] === T_STATIC ) { |
133 | if ( $containsNonStaticStatements ) { |
134 | $fix = $phpcsFile->addFixableError( |
135 | 'Cannot not use static closure in class context', |
136 | $stackPtr, |
137 | 'NonStaticClosure' |
138 | ); |
139 | if ( $fix ) { |
140 | $phpcsFile->fixer->beginChangeset(); |
141 | |
142 | do { |
143 | $phpcsFile->fixer->replaceToken( $prevClosureToken, '' ); |
144 | $prevClosureToken++; |
145 | } while ( $prevClosureToken < $stackPtr ); |
146 | |
147 | $phpcsFile->fixer->endChangeset(); |
148 | } |
149 | } |
150 | } elseif ( !$containsNonStaticStatements ) { |
151 | $fix = $phpcsFile->addFixableWarning( |
152 | 'Use static closure', |
153 | $stackPtr, |
154 | 'StaticClosure' |
155 | ); |
156 | if ( $fix ) { |
157 | $phpcsFile->fixer->addContentBefore( $stackPtr, 'static ' ); |
158 | } |
159 | } |
160 | |
161 | // also check inner closure for static |
162 | } |
163 | |
164 | /** |
165 | * Check if this is a class property like const or static field of format self::const |
166 | * @param File $phpcsFile |
167 | * @param array $tokens |
168 | * @param int &$stackPtr Non-empty token after self/parent/static |
169 | * @param int $end |
170 | * @return bool |
171 | */ |
172 | private function isStaticClassProperty( File $phpcsFile, array $tokens, int &$stackPtr, int $end ): bool { |
173 | // No ::, no const |
174 | if ( $tokens[$stackPtr]['code'] !== T_DOUBLE_COLON ) { |
175 | return false; |
176 | } |
177 | |
178 | // the const is a T_STRING, but also method calls are T_STRING |
179 | // okay with (static) variables |
180 | $stackPtr = $phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, $end, true ); |
181 | if ( $stackPtr === false || $tokens[$stackPtr]['code'] !== T_STRING ) { |
182 | return $stackPtr !== false && $tokens[$stackPtr]['code'] === T_VARIABLE; |
183 | } |
184 | |
185 | // const is a T_STRING without parenthesis after it |
186 | $stackPtr = $phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, $end, true ); |
187 | return $stackPtr !== false && $tokens[$stackPtr]['code'] !== T_OPEN_PARENTHESIS; |
188 | } |
189 | } |