Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 126 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
PropertyDocumentationSniff | |
0.00% |
0 / 126 |
|
0.00% |
0 / 3 |
1332 | |
0.00% |
0 / 1 |
register | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
process | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
380 | |||
processVar | |
0.00% |
0 / 82 |
|
0.00% |
0 / 1 |
272 |
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 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @license GPL-2.0-or-later |
19 | * @file |
20 | */ |
21 | |
22 | namespace MediaWiki\Sniffs\Commenting; |
23 | |
24 | use PHP_CodeSniffer\Files\File; |
25 | use PHP_CodeSniffer\Sniffs\Sniff; |
26 | use PHP_CodeSniffer\Util\Tokens; |
27 | |
28 | class PropertyDocumentationSniff implements Sniff { |
29 | |
30 | use DocumentationTypeTrait; |
31 | |
32 | /** |
33 | * @inheritDoc |
34 | */ |
35 | public function register(): array { |
36 | return [ T_VARIABLE ]; |
37 | } |
38 | |
39 | /** |
40 | * Processes this test, when one of its tokens is encountered. |
41 | * |
42 | * @param File $phpcsFile The file being scanned. |
43 | * @param int $stackPtr The position of the current token in the stack passed in $tokens. |
44 | * |
45 | * @return void |
46 | */ |
47 | public function process( File $phpcsFile, $stackPtr ) { |
48 | $tokens = $phpcsFile->getTokens(); |
49 | |
50 | // Only for class properties |
51 | $scopes = array_keys( $tokens[$stackPtr]['conditions'] ); |
52 | $scope = array_pop( $scopes ); |
53 | if ( isset( $tokens[$stackPtr]['nested_parenthesis'] ) |
54 | || $scope === null |
55 | || ( $tokens[$scope]['code'] !== T_CLASS && $tokens[$scope]['code'] !== T_TRAIT ) |
56 | ) { |
57 | return; |
58 | } |
59 | |
60 | $find = Tokens::$emptyTokens; |
61 | $find[] = T_STATIC; |
62 | $find[] = T_NULLABLE; |
63 | $find[] = T_STRING; |
64 | $visibilityPtr = $phpcsFile->findPrevious( $find, $stackPtr - 1, null, true ); |
65 | if ( !$visibilityPtr || ( $tokens[$visibilityPtr]['code'] !== T_VAR && |
66 | !isset( Tokens::$scopeModifiers[ $tokens[$visibilityPtr]['code'] ] ) ) |
67 | ) { |
68 | return; |
69 | } |
70 | $commentEnd = $phpcsFile->findPrevious( [ T_WHITESPACE ], $visibilityPtr - 1, null, true ); |
71 | if ( $tokens[$commentEnd]['code'] === T_COMMENT ) { |
72 | // Inline comments might just be closing comments for |
73 | // control structures or functions instead of function comments |
74 | // using the wrong comment type. If there is other code on the line, |
75 | // assume they relate to that code. |
76 | $prev = $phpcsFile->findPrevious( $find, $commentEnd - 1, null, true ); |
77 | if ( $prev !== false && $tokens[$prev]['line'] === $tokens[$commentEnd]['line'] ) { |
78 | $commentEnd = $prev; |
79 | } |
80 | } |
81 | if ( $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG |
82 | && $tokens[$commentEnd]['code'] !== T_COMMENT |
83 | ) { |
84 | $memberProps = $phpcsFile->getMemberProperties( $stackPtr ); |
85 | if ( $memberProps['type'] === '' ) { |
86 | $phpcsFile->addError( |
87 | 'Missing class property doc comment', |
88 | $stackPtr, |
89 | // Messages used: MissingDocumentationPublic, MissingDocumentationProtected, |
90 | // MissingDocumentationPrivate |
91 | 'MissingDocumentation' . ucfirst( $memberProps['scope'] ) |
92 | ); |
93 | } |
94 | return; |
95 | } |
96 | if ( $tokens[$commentEnd]['code'] === T_COMMENT ) { |
97 | $phpcsFile->addError( 'You must use "/**" style comments for a class property comment', |
98 | $stackPtr, 'WrongStyle' ); |
99 | return; |
100 | } |
101 | if ( $tokens[$commentEnd]['line'] !== $tokens[$visibilityPtr]['line'] - 1 ) { |
102 | $error = 'There must be no blank lines after the class property comment'; |
103 | $phpcsFile->addError( $error, $commentEnd, 'SpacingAfter' ); |
104 | } |
105 | $commentStart = $tokens[$commentEnd]['comment_opener']; |
106 | foreach ( $tokens[$commentStart]['comment_tags'] as $tag ) { |
107 | $tagText = $tokens[$tag]['content']; |
108 | if ( strcasecmp( $tagText, '@inheritDoc' ) === 0 || $tagText === '@deprecated' ) { |
109 | // No need to validate deprecated properties or those that inherit |
110 | // their documentation |
111 | return; |
112 | } |
113 | } |
114 | |
115 | $this->processVar( $phpcsFile, $commentStart, $stackPtr ); |
116 | } |
117 | |
118 | /** |
119 | * Process the var doc comments. |
120 | * |
121 | * @param File $phpcsFile The file being scanned. |
122 | * @param int $commentStart The position in the stack where the comment started. |
123 | * @param int $stackPtr The position in the stack where the property itself started (T_VARIABLE) |
124 | */ |
125 | private function processVar( File $phpcsFile, int $commentStart, int $stackPtr ): void { |
126 | $tokens = $phpcsFile->getTokens(); |
127 | $var = null; |
128 | foreach ( $tokens[$commentStart]['comment_tags'] as $ptr ) { |
129 | $tag = $tokens[$ptr]['content']; |
130 | if ( $tag !== '@var' ) { |
131 | continue; |
132 | } |
133 | if ( $var ) { |
134 | $error = 'Only 1 @var tag is allowed in a class property comment'; |
135 | $phpcsFile->addError( $error, $ptr, 'DuplicateVar' ); |
136 | return; |
137 | } |
138 | $var = $ptr; |
139 | } |
140 | if ( $var !== null ) { |
141 | $varTypeSpacing = $var + 1; |
142 | // Check spaces before var |
143 | if ( $tokens[$varTypeSpacing]['code'] === T_DOC_COMMENT_WHITESPACE ) { |
144 | $expectedSpaces = 1; |
145 | $currentSpaces = strlen( $tokens[$varTypeSpacing]['content'] ); |
146 | if ( $currentSpaces !== $expectedSpaces ) { |
147 | $fix = $phpcsFile->addFixableWarning( |
148 | 'Expected %s spaces before var type; %s found', |
149 | $varTypeSpacing, |
150 | 'SpacingBeforeVarType', |
151 | [ $expectedSpaces, $currentSpaces ] |
152 | ); |
153 | if ( $fix ) { |
154 | $phpcsFile->fixer->replaceToken( $varTypeSpacing, ' ' ); |
155 | } |
156 | } |
157 | } |
158 | $varType = $var + 2; |
159 | $content = ''; |
160 | if ( $tokens[$varType]['code'] === T_DOC_COMMENT_STRING ) { |
161 | $content = $tokens[$varType]['content']; |
162 | } |
163 | if ( $content === '' ) { |
164 | $error = 'Var type missing for @var tag in class property comment'; |
165 | $phpcsFile->addError( $error, $var, 'MissingVarType' ); |
166 | return; |
167 | } |
168 | [ $type, $separatorLength, $comment ] = $this->splitTypeAndComment( $content ); |
169 | $fixType = false; |
170 | // Check for unneeded punctuation |
171 | $type = $this->fixTrailingPunctuation( |
172 | $phpcsFile, |
173 | $varType, |
174 | $type, |
175 | $fixType, |
176 | 'var type' |
177 | ); |
178 | $type = $this->fixWrappedParenthesis( |
179 | $phpcsFile, |
180 | $varType, |
181 | $type, |
182 | $fixType, |
183 | 'var type' |
184 | ); |
185 | // Check the type for short types |
186 | $type = $this->fixShortTypes( $phpcsFile, $varType, $type, $fixType, 'var' ); |
187 | $this->maybeAddObjectTypehintError( |
188 | $phpcsFile, |
189 | $varType, |
190 | $type, |
191 | 'var' |
192 | ); |
193 | $this->maybeAddTypeTypehintError( |
194 | $phpcsFile, |
195 | $varType, |
196 | $type, |
197 | 'var' |
198 | ); |
199 | // Check spacing after type |
200 | if ( $comment !== '' ) { |
201 | $expectedSpaces = 1; |
202 | if ( $separatorLength !== $expectedSpaces ) { |
203 | $fix = $phpcsFile->addFixableWarning( |
204 | 'Expected %s spaces after var type; %s found', |
205 | $varType, |
206 | 'SpacingAfterVarType', |
207 | [ $expectedSpaces, $separatorLength ] |
208 | ); |
209 | if ( $fix ) { |
210 | $fixType = true; |
211 | $separatorLength = $expectedSpaces; |
212 | } |
213 | } |
214 | } |
215 | if ( $fixType ) { |
216 | $phpcsFile->fixer->replaceToken( |
217 | $varType, |
218 | $type . ( $comment !== '' ? str_repeat( ' ', $separatorLength ) . $comment : '' ) |
219 | ); |
220 | } |
221 | } elseif ( $phpcsFile->getMemberProperties( $stackPtr )['type'] === '' ) { |
222 | $error = 'Missing type or @var tag in class property comment'; |
223 | $phpcsFile->addError( $error, $tokens[$commentStart]['comment_closer'], 'MissingVar' ); |
224 | } |
225 | } |
226 | |
227 | } |