Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 75 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
DeprecatedPHPUnitMethodsSniff | |
0.00% |
0 / 75 |
|
0.00% |
0 / 5 |
702 | |
0.00% |
0 / 1 |
register | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
process | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
240 | |||
handleAssertInternalType | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
72 | |||
handleAssertArraySubset | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
handleAttributeMethod | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
16 | */ |
17 | |
18 | namespace MediaWiki\Sniffs\PHPUnit; |
19 | |
20 | use LogicException; |
21 | use PHP_CodeSniffer\Files\File; |
22 | use PHP_CodeSniffer\Sniffs\Sniff; |
23 | use PHP_CodeSniffer\Util\Tokens; |
24 | |
25 | /** |
26 | * Forbids usage of PHPUnit methods deprecated/removed in PHPUnit8 |
27 | * @fixme Avoid duplication with other PHPUnit-related sniffs |
28 | */ |
29 | class DeprecatedPHPUnitMethodsSniff implements Sniff { |
30 | use PHPUnitTestTrait; |
31 | |
32 | private const FORBIDDEN_ATTRIBUTE_METHODS = [ |
33 | 'assertAttributeContains', |
34 | 'assertAttributeNotContains', |
35 | 'assertAttributeContainsOnly', |
36 | 'assertAttributeNotContainsOnly', |
37 | 'assertAttributeCount', |
38 | 'assertAttributeNotCount', |
39 | 'assertAttributeEquals', |
40 | 'assertAttributeNotEquals', |
41 | 'assertAttributeEmpty', |
42 | 'assertAttributeNotEmpty', |
43 | 'assertAttributeGreaterThan', |
44 | 'assertAttributeGreaterThanOrEqual', |
45 | 'assertAttributeLessThan', |
46 | 'assertAttributeLessThanOrEqual', |
47 | 'assertAttributeSame', |
48 | 'assertAttributeNotSame', |
49 | 'assertAttributeInstanceOf', |
50 | 'assertAttributeNotInstanceOf', |
51 | 'assertAttributeInternalType', |
52 | 'assertAttributeNotInternalType', |
53 | 'attribute', |
54 | 'attributeEqualTo', |
55 | 'readAttribute', |
56 | 'getStaticAttribute', |
57 | 'getObjectAttribute', |
58 | ]; |
59 | |
60 | private const CHECK_METHODS = [ |
61 | // FORBIDDEN_ATTRIBUTE_METHODS |
62 | 'assertInternalType', |
63 | 'assertNotInternalType', |
64 | 'assertType', |
65 | 'assertArraySubset', |
66 | // No assertEquals because we cannot do type checking |
67 | ]; |
68 | |
69 | /** |
70 | * Replacements for assertInternalType, see IsType::KNOWN_TYPES. |
71 | * The * inside should be replaced either with 'Not' or '' depending |
72 | * on whether the call is to assertInternalType or assertNotInternalType |
73 | */ |
74 | private const INTERNAL_TYPES_REPLACEMENTS = [ |
75 | 'array' => 'assertIs*Array', |
76 | 'boolean' => 'assertIs*Bool', |
77 | 'bool' => 'assertIs*Bool', |
78 | 'double' => 'assertIs*Float', |
79 | 'float' => 'assertIs*Float', |
80 | 'integer' => 'assertIs*Int', |
81 | 'int' => 'assertIs*Int', |
82 | 'null' => 'assert*Null', |
83 | 'numeric' => 'assertIs*Numeric', |
84 | 'object' => 'assertIs*Object', |
85 | 'real' => 'assertIs*Float', |
86 | 'resource' => 'assertIs*Resource', |
87 | 'string' => 'assertIs*String', |
88 | 'scalar' => 'assertIs*Scalar', |
89 | 'callable' => 'assertIs*Callable', |
90 | 'iterable' => 'assertIs*Iterable', |
91 | ]; |
92 | |
93 | /** @var File */ |
94 | private File $file; |
95 | |
96 | /** @var array */ |
97 | private array $tokens; |
98 | |
99 | /** |
100 | * @inheritDoc |
101 | */ |
102 | public function register(): array { |
103 | return [ T_CLASS ]; |
104 | } |
105 | |
106 | /** |
107 | * @param File $phpcsFile |
108 | * @param int $stackPtr Position of extends token |
109 | * @return void|int |
110 | */ |
111 | public function process( File $phpcsFile, $stackPtr ) { |
112 | if ( !$this->isTestFile( $phpcsFile, $stackPtr ) ) { |
113 | return $phpcsFile->numTokens; |
114 | } |
115 | |
116 | $this->file = $phpcsFile; |
117 | $this->tokens = $phpcsFile->getTokens(); |
118 | |
119 | $tokens = $phpcsFile->getTokens(); |
120 | $startTok = $tokens[$stackPtr]; |
121 | $cur = $startTok['scope_opener']; |
122 | $end = $startTok['scope_closer']; |
123 | $checkMethods = array_merge( self::CHECK_METHODS, self::FORBIDDEN_ATTRIBUTE_METHODS ); |
124 | $nextToken = [ T_PAAMAYIM_NEKUDOTAYIM, T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR ]; |
125 | |
126 | $cur = $phpcsFile->findNext( $nextToken, $cur + 1, $end ); |
127 | while ( $cur !== false ) { |
128 | $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, $cur - 1, null, true ); |
129 | if ( |
130 | $prev && ( |
131 | ( $tokens[$prev]['code'] === T_VARIABLE && $tokens[$prev]['content'] === '$this' ) || |
132 | in_array( $tokens[$prev]['code'], [ T_STATIC, T_SELF ], true ) |
133 | ) |
134 | ) { |
135 | $funcTok = $phpcsFile->findNext( Tokens::$emptyTokens, $cur + 1, null, true ); |
136 | |
137 | if ( |
138 | $tokens[$funcTok]['code'] === T_STRING && |
139 | in_array( $tokens[$funcTok]['content'], $checkMethods, true ) |
140 | ) { |
141 | $fname = $tokens[$funcTok]['content']; |
142 | switch ( $fname ) { |
143 | case 'assertArraySubset': |
144 | $this->handleAssertArraySubset( $funcTok ); |
145 | break; |
146 | case 'assertType': |
147 | // MediaWiki's own variant to make things more complicated. |
148 | case 'assertInternalType': |
149 | case 'assertNotInternalType': |
150 | $this->handleAssertInternalType( $fname, $funcTok ); |
151 | break; |
152 | default: |
153 | if ( in_array( $fname, self::FORBIDDEN_ATTRIBUTE_METHODS, true ) ) { |
154 | $this->handleAttributeMethod( $fname, $funcTok ); |
155 | } else { |
156 | throw new LogicException( "Unhandled case $fname" ); |
157 | } |
158 | } |
159 | } |
160 | $cur = $funcTok; |
161 | } |
162 | |
163 | $cur = $phpcsFile->findNext( $nextToken, $cur + 1, $end ); |
164 | } |
165 | } |
166 | |
167 | /** |
168 | * @param string $funcName Either assertInternalType, assertNotInternalType, or assertType |
169 | * @param int $funcPos Token position of the function call |
170 | */ |
171 | private function handleAssertInternalType( string $funcName, int $funcPos ): void { |
172 | $not = $funcName === 'assertNotInternalType' ? 'Not' : ''; |
173 | if ( $funcName === 'assertType' ) { |
174 | $err = 'MediaWikiIntegrationTestCase::assertType was deprecated in MW 1.35.'; |
175 | $data = []; |
176 | } else { |
177 | $err = 'The PHPUnit method assert%sInternalType() was deprecated in PHPUnit 8.'; |
178 | $data = [ $not ]; |
179 | } |
180 | |
181 | $parensToken = $this->file->findNext( T_WHITESPACE, $funcPos + 1, null, true ); |
182 | if ( $this->tokens[$parensToken]['code'] !== T_OPEN_PARENTHESIS ) { |
183 | return; |
184 | } |
185 | |
186 | $argToken = $this->file->findNext( T_WHITESPACE, $parensToken + 1, null, true ); |
187 | if ( $this->tokens[$argToken]['code'] !== T_CONSTANT_ENCAPSED_STRING ) { |
188 | // Probably a variable. |
189 | $this->file->addError( $err, $funcPos, 'AssertInternalTypeGeneric', $data ); |
190 | return; |
191 | } |
192 | |
193 | $type = trim( $this->tokens[$argToken]['content'], '"\'' ); |
194 | if ( !array_key_exists( $type, self::INTERNAL_TYPES_REPLACEMENTS ) ) { |
195 | // If it happens for assert(Not)InternalType, it's likely a bug, so report it. |
196 | // If it happens for assertType, report it all the same because the method is deprecated. |
197 | $this->file->addError( $err, $funcPos, 'AssertInternalTypeGeneric', $data ); |
198 | return; |
199 | } |
200 | |
201 | $commaToken = $this->file->findNext( T_WHITESPACE, $argToken + 1, null, true ); |
202 | if ( $this->tokens[$commaToken]['code'] !== T_COMMA ) { |
203 | // WTF? This will fail anyway. |
204 | $this->file->addError( $err, $funcPos, 'AssertInternalTypeGeneric', $data ); |
205 | return; |
206 | } |
207 | |
208 | $replacement = str_replace( '*', $not, self::INTERNAL_TYPES_REPLACEMENTS[$type] ); |
209 | $err .= ' Use %s() instead.'; |
210 | $data[] = $replacement; |
211 | $fix = $this->file->addFixableError( |
212 | $err, |
213 | $funcPos, |
214 | 'AssertInternalTypeLiteral', |
215 | $data |
216 | ); |
217 | if ( $fix ) { |
218 | $this->file->fixer->replaceToken( $funcPos, $replacement ); |
219 | $this->file->fixer->replaceToken( $argToken, '' ); |
220 | $this->file->fixer->replaceToken( $commaToken, '' ); |
221 | } |
222 | } |
223 | |
224 | /** |
225 | * Deprecated in PHPUnit8 with no alternative. People should either use a workaround, |
226 | * or we should start requiring phpunit-arraysubset-asserts |
227 | * |
228 | * @param int $funcPos |
229 | * @suppress PhanUnusedPrivateMethodParameter Refers to the fixme |
230 | */ |
231 | private function handleAssertArraySubset( int $funcPos ): void { |
232 | // FIXME: What to do here? Remove/update/re-enable... T192167#5685401 |
233 | /* |
234 | $this->file->addError( |
235 | 'The PHPUnit method assertArraySubset() was deprecated in PHPUnit 8.', |
236 | $funcPos, |
237 | 'AssertArraySubset' |
238 | ); |
239 | */ |
240 | } |
241 | |
242 | /** |
243 | * This huge list of methods was deprecated in PHPUnit8 with no alternative. |
244 | * |
245 | * @param string $funcName |
246 | * @param int $funcPos |
247 | */ |
248 | private function handleAttributeMethod( string $funcName, int $funcPos ): void { |
249 | $this->file->addError( |
250 | 'The PHPUnit method %s() was deprecated in PHPUnit 8.', |
251 | $funcPos, |
252 | 'AttributeMethods', |
253 | [ $funcName ] |
254 | ); |
255 | } |
256 | } |