Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 44 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
MissingCoversSniff | |
0.00% |
0 / 44 |
|
0.00% |
0 / 3 |
240 | |
0.00% |
0 / 1 |
register | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
process | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
72 | |||
hasCoversTags | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | /** |
3 | * Copyright (C) 2015 WordPoints |
4 | * Copyright (C) 2018 Kunal Mehta <legoktm@member.fsf.org> |
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 | */ |
20 | |
21 | namespace MediaWiki\Sniffs\Commenting; |
22 | |
23 | use PHP_CodeSniffer\Files\File; |
24 | use PHP_CodeSniffer\Sniffs\Sniff; |
25 | use PHP_CodeSniffer\Util\Tokens; |
26 | |
27 | /** |
28 | * Identify Test classes that do not have |
29 | * any @covers tags |
30 | */ |
31 | class MissingCoversSniff implements Sniff { |
32 | |
33 | /** |
34 | * @inheritDoc |
35 | */ |
36 | public function register(): array { |
37 | return [ T_CLASS ]; |
38 | } |
39 | |
40 | /** |
41 | * @param File $phpcsFile |
42 | * @param int $stackPtr Position of T_CLASS |
43 | * @return void |
44 | */ |
45 | public function process( File $phpcsFile, $stackPtr ) { |
46 | $name = $phpcsFile->getDeclarationName( $stackPtr ); |
47 | if ( !str_ends_with( $name, 'Test' ) ) { |
48 | // Only want to validate classes that end in test |
49 | return; |
50 | } |
51 | $props = $phpcsFile->getClassProperties( $stackPtr ); |
52 | if ( $props['is_abstract'] ) { |
53 | // No point in requiring @covers from an abstract class |
54 | return; |
55 | } |
56 | |
57 | $classCovers = $this->hasCoversTags( $phpcsFile, $stackPtr ); |
58 | if ( $classCovers ) { |
59 | // The class has a @covers tag, awesome. |
60 | return; |
61 | } |
62 | |
63 | // Check each individual test function |
64 | $tokens = $phpcsFile->getTokens(); |
65 | $classCloser = $tokens[$stackPtr]['scope_closer']; |
66 | $funcPtr = $stackPtr; |
67 | while ( true ) { |
68 | $funcPtr = $phpcsFile->findNext( [ T_FUNCTION ], $funcPtr + 1, $classCloser ); |
69 | if ( !$funcPtr ) { |
70 | // No more |
71 | break; |
72 | } |
73 | |
74 | $name = $phpcsFile->getDeclarationName( $funcPtr ); |
75 | if ( !str_starts_with( $name, 'test' ) ) { |
76 | // If it doesn't start with "test", skip |
77 | continue; |
78 | } |
79 | |
80 | $hasCovers = $this->hasCoversTags( $phpcsFile, $funcPtr ); |
81 | if ( !$hasCovers ) { |
82 | $phpcsFile->addWarning( |
83 | 'The %s test method has no @covers tags', |
84 | $funcPtr, 'MissingCovers', [ $name ] |
85 | ); |
86 | } |
87 | } |
88 | } |
89 | |
90 | /** |
91 | * Whether the statement has @covers tags |
92 | * |
93 | * @param File $phpcsFile |
94 | * @param int $stackPtr Position of T_CLASS/T_FUNCTION |
95 | * |
96 | * @return bool |
97 | */ |
98 | protected function hasCoversTags( File $phpcsFile, int $stackPtr ): bool { |
99 | $exclude = array_merge( |
100 | Tokens::$methodPrefixes, |
101 | [ T_WHITESPACE ] |
102 | ); |
103 | $closer = $phpcsFile->findPrevious( $exclude, $stackPtr - 1, 0, true ); |
104 | if ( $closer === false ) { |
105 | return false; |
106 | } |
107 | $tokens = $phpcsFile->getTokens(); |
108 | $token = $tokens[$closer]; |
109 | if ( $token['code'] !== T_DOC_COMMENT_CLOSE_TAG ) { |
110 | // No doc comment |
111 | return false; |
112 | } |
113 | |
114 | $opener = $tokens[$closer]['comment_opener']; |
115 | $tags = $tokens[$opener]['comment_tags']; |
116 | foreach ( $tags as $tag ) { |
117 | $name = $tokens[$tag]['content']; |
118 | if ( $name === '@covers' || $name === '@coversNothing' ) { |
119 | return true; |
120 | } |
121 | } |
122 | |
123 | return false; |
124 | } |
125 | |
126 | } |