Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 73 |
|
0.00% |
0 / 2 |
CRAP | |
0.00% |
0 / 1 |
AssertEqualsSniff | |
0.00% |
0 / 73 |
|
0.00% |
0 / 2 |
1406 | |
0.00% |
0 / 1 |
register | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
process | |
0.00% |
0 / 72 |
|
0.00% |
0 / 1 |
1332 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Sniffs\PHPUnit; |
4 | |
5 | use PHP_CodeSniffer\Files\File; |
6 | use PHP_CodeSniffer\Sniffs\Sniff; |
7 | |
8 | /** |
9 | * Discourages the use of PHPUnit's relaxed, not type-safe assertEquals() in favor of strict |
10 | * alternatives like assertSame(), assertNull(), and such. Please note: The auto-fixes done by this |
11 | * sniff can make PHPUnit test cases fail. These should be fixed not by reverting the fix, but by |
12 | * finding better expected values or better assertions. |
13 | * |
14 | * @author Thiemo Kreuz |
15 | * @license GPL-2.0-or-later |
16 | */ |
17 | class AssertEqualsSniff implements Sniff { |
18 | use PHPUnitTestTrait; |
19 | |
20 | private const ASSERTIONS = [ |
21 | 'assertEquals' => true, |
22 | 'assertNotEquals' => true, |
23 | 'assertNotSame' => true, |
24 | ]; |
25 | |
26 | /** |
27 | * @inheritDoc |
28 | */ |
29 | public function register(): array { |
30 | return [ T_STRING ]; |
31 | } |
32 | |
33 | /** |
34 | * @param File $phpcsFile |
35 | * @param int $stackPtr |
36 | * |
37 | * @return void|int |
38 | */ |
39 | public function process( File $phpcsFile, $stackPtr ) { |
40 | if ( !$this->isTestFile( $phpcsFile, $stackPtr ) ) { |
41 | return $phpcsFile->numTokens; |
42 | } |
43 | |
44 | $tokens = $phpcsFile->getTokens(); |
45 | $assertion = $tokens[$stackPtr]['content']; |
46 | |
47 | // We don't care about stuff that's not in a method in a class |
48 | if ( $tokens[$stackPtr]['level'] < 2 || !isset( self::ASSERTIONS[$assertion] ) ) { |
49 | return; |
50 | } |
51 | |
52 | $opener = $phpcsFile->findNext( T_WHITESPACE, $stackPtr + 1, null, true ); |
53 | // Looks like this string is not a method call |
54 | if ( !isset( $tokens[$opener]['parenthesis_closer'] ) ) { |
55 | return $opener; |
56 | } |
57 | |
58 | $isAssertEquals = $assertion === 'assertEquals'; |
59 | $expected = $phpcsFile->findNext( T_WHITESPACE, $opener + 1, null, true ); |
60 | $msg = '%s accepts many non-%s values, please use strict alternatives like %s'; |
61 | /** @var bool|string $fix */ |
62 | $fix = false; |
63 | |
64 | switch ( $tokens[$expected]['code'] ) { |
65 | case T_NULL: |
66 | if ( !$isAssertEquals ) { |
67 | break; |
68 | } |
69 | |
70 | $msgParams = [ $assertion, 'null', 'assertNull' ]; |
71 | if ( $phpcsFile->addFixableWarning( $msg, $stackPtr, 'Null', $msgParams ) ) { |
72 | $fix = 'assertNull'; |
73 | } |
74 | break; |
75 | |
76 | case T_FALSE: |
77 | $replacement = $isAssertEquals ? 'assertFalse' : 'assertTrue'; |
78 | $msgParams = [ $assertion, $isAssertEquals ? 'false' : 'true', $replacement ]; |
79 | if ( $phpcsFile->addFixableWarning( $msg, $stackPtr, 'False', $msgParams ) ) { |
80 | $fix = $replacement; |
81 | } |
82 | break; |
83 | |
84 | case T_TRUE: |
85 | $replacement = $isAssertEquals ? 'assertTrue' : 'assertFalse'; |
86 | $msgParams = [ $assertion, $isAssertEquals ? 'true' : 'false', $replacement ]; |
87 | if ( $phpcsFile->addFixableWarning( $msg, $stackPtr, 'True', $msgParams ) ) { |
88 | $fix = $replacement; |
89 | } |
90 | break; |
91 | |
92 | case T_LNUMBER: |
93 | if ( !$isAssertEquals ) { |
94 | break; |
95 | } |
96 | |
97 | $number = (int)$tokens[$expected]['content']; |
98 | if ( $number === 0 || $number === 1 ) { |
99 | $msgParams = [ $assertion, $number ? 'numeric' : 'zero', 'assertSame' ]; |
100 | $fix = $phpcsFile->addFixableWarning( $msg, $stackPtr, 'Int', $msgParams ); |
101 | } |
102 | break; |
103 | |
104 | case T_DNUMBER: |
105 | if ( !$isAssertEquals ) { |
106 | break; |
107 | } |
108 | |
109 | $number = (float)$tokens[$expected]['content']; |
110 | if ( $number === 0.0 || $number === 1.0 ) { |
111 | $msgParams = [ $assertion, $number ? 'numeric' : 'zero', 'assertSame' ]; |
112 | $fix = $phpcsFile->addFixableWarning( $msg, $stackPtr, 'Float', $msgParams ); |
113 | } |
114 | break; |
115 | |
116 | case T_CONSTANT_ENCAPSED_STRING: |
117 | if ( !$isAssertEquals ) { |
118 | break; |
119 | } |
120 | |
121 | $msgParams = [ $assertion, 'string', 'assertSame' ]; |
122 | |
123 | // The empty string as well as "0" are among PHP's "falsy" values |
124 | if ( strlen( $tokens[$expected]['content'] ) <= 2 || |
125 | $tokens[$expected]['content'][1] === '0' |
126 | ) { |
127 | $fix = $phpcsFile->addFixableWarning( $msg, $stackPtr, 'FalsyString', $msgParams ); |
128 | break; |
129 | } |
130 | |
131 | $string = trim( substr( $tokens[$expected]['content'], 1, -1 ) ); |
132 | if ( ctype_digit( $string ) ) { |
133 | $fix = $phpcsFile->addFixableWarning( $msg, $stackPtr, 'IntegerString', $msgParams ); |
134 | } elseif ( is_numeric( $string ) ) { |
135 | $fix = $phpcsFile->addFixableWarning( $msg, $stackPtr, 'NumericString', $msgParams ); |
136 | } |
137 | } |
138 | |
139 | $fixer = $phpcsFile->fixer; |
140 | // Fall back to assertSame instead of blindly removing unknown tokens |
141 | if ( is_string( $fix ) && $tokens[$expected + 1]['code'] === T_COMMA ) { |
142 | $fixer->replaceToken( $stackPtr, $fix ); |
143 | $fixer->replaceToken( $expected, '' ); |
144 | $fixer->replaceToken( $expected + 1, '' ); |
145 | if ( $tokens[$expected + 2]['code'] === T_WHITESPACE ) { |
146 | $fixer->replaceToken( $expected + 2, '' ); |
147 | } |
148 | } elseif ( $fix ) { |
149 | $fixer->replaceToken( $stackPtr, 'assertSame' ); |
150 | } |
151 | |
152 | // There is no way the next assertEquals() can be closer than this |
153 | return $tokens[$opener]['parenthesis_closer'] + 4; |
154 | } |
155 | |
156 | } |