Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 76 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
DocumentationTypeTrait | |
0.00% |
0 / 76 |
|
0.00% |
0 / 6 |
992 | |
0.00% |
0 / 1 |
splitTypeAndComment | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
132 | |||
fixShortTypes | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
42 | |||
fixTrailingPunctuation | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
fixWrappedParenthesis | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
maybeAddObjectTypehintError | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
maybeAddTypeTypehintError | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 |
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 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Sniffs\Commenting; |
22 | |
23 | use PHP_CodeSniffer\Files\File; |
24 | |
25 | /** |
26 | * Share code between FunctionCommentSniff and PropertyDocumentationSniff |
27 | */ |
28 | trait DocumentationTypeTrait { |
29 | |
30 | /** |
31 | * Mapping for swap short types |
32 | * @var string[] |
33 | */ |
34 | private static $SHORT_TYPE_MAPPING = [ |
35 | // @phan-suppress-previous-line PhanReadOnlyPrivateProperty Traits cannot have constants |
36 | 'boolean' => 'bool', |
37 | 'boolean[]' => 'bool[]', |
38 | 'integer' => 'int', |
39 | 'integer[]' => 'int[]', |
40 | ]; |
41 | |
42 | /** |
43 | * Mapping for primitive types to case correct |
44 | * Cannot just detect case due to classes being uppercase |
45 | * |
46 | * @var string[] |
47 | */ |
48 | private static $PRIMITIVE_TYPE_MAPPING = [ |
49 | // @phan-suppress-previous-line PhanReadOnlyPrivateProperty Traits cannot have constants |
50 | 'Array' => 'array', |
51 | 'Array[]' => 'array[]', |
52 | 'Bool' => 'bool', |
53 | 'Bool[]' => 'bool[]', |
54 | 'Float' => 'float', |
55 | 'Float[]' => 'float[]', |
56 | 'Int' => 'int', |
57 | 'Int[]' => 'int[]', |
58 | 'Mixed' => 'mixed', |
59 | 'Mixed[]' => 'mixed[]', |
60 | 'Null' => 'null', |
61 | 'NULL' => 'null', |
62 | 'Null[]' => 'null[]', |
63 | 'NULL[]' => 'null[]', |
64 | 'Object' => 'object', |
65 | 'Object[]' => 'object[]', |
66 | 'String' => 'string', |
67 | 'String[]' => 'string[]', |
68 | 'Callable' => 'callable', |
69 | 'Callable[]' => 'callable[]', |
70 | ]; |
71 | |
72 | /** |
73 | * Split PHPDoc comment strings like "bool[] Comment" into type and comment, while respecting |
74 | * types like `array<int, array<string, bool>>` and `array{id: int, name: string}`. |
75 | * |
76 | * @param string $str |
77 | * |
78 | * @return array [ string $type, int|null $separatorLength, string $comment ] |
79 | */ |
80 | private function splitTypeAndComment( string $str ): array { |
81 | $brackets = 0; |
82 | $curly = 0; |
83 | $len = strlen( $str ); |
84 | for ( $i = 0; $i < $len; $i++ ) { |
85 | $char = $str[$i]; |
86 | // Stop at the first space that is not part of a valid pair of brackets |
87 | if ( $char === ' ' && !$brackets && !$curly ) { |
88 | $separatorLength = strspn( $str, ' ', $i ); |
89 | return [ substr( $str, 0, $i ), $separatorLength, substr( $str, $i + $separatorLength ) ]; |
90 | } elseif ( $char === '>' && $brackets ) { |
91 | $brackets--; |
92 | } elseif ( $char === '<' ) { |
93 | $brackets++; |
94 | } elseif ( $char === '}' && $curly ) { |
95 | $curly--; |
96 | } elseif ( $char === '{' ) { |
97 | $curly++; |
98 | } |
99 | } |
100 | return [ $str, null, '' ]; |
101 | } |
102 | |
103 | /** |
104 | * @param File $phpcsFile |
105 | * @param int $stackPtr |
106 | * @param string $typesString |
107 | * @param bool &$fix Set when autofix is needed |
108 | * @param string $annotation Either "param" or "return" or "var" |
109 | * @return string Updated $typesString |
110 | */ |
111 | private function fixShortTypes( |
112 | File $phpcsFile, |
113 | int $stackPtr, |
114 | string $typesString, |
115 | bool &$fix, |
116 | string $annotation |
117 | ): string { |
118 | $typeList = explode( '|', $typesString ); |
119 | foreach ( $typeList as &$type ) { |
120 | // Corrects long types from both upper and lowercase to lowercase shorttype |
121 | $key = lcfirst( $type ); |
122 | if ( isset( self::$SHORT_TYPE_MAPPING[$key] ) ) { |
123 | $type = self::$SHORT_TYPE_MAPPING[$key]; |
124 | $code = 'NotShort' . str_replace( '[]', 'Array', ucfirst( $type ) ) . ucfirst( $annotation ); |
125 | $fix = $phpcsFile->addFixableError( |
126 | 'Short type of "%s" should be used for @%s tag', |
127 | $stackPtr, |
128 | $code, |
129 | [ $type, $annotation ] |
130 | ) || $fix; |
131 | } elseif ( isset( self::$PRIMITIVE_TYPE_MAPPING[$type] ) ) { |
132 | $type = self::$PRIMITIVE_TYPE_MAPPING[$type]; |
133 | $code = 'UppercasePrimitive' . str_replace( '[]', 'Array', ucfirst( $type ) ) . ucfirst( $annotation ); |
134 | $fix = $phpcsFile->addFixableError( |
135 | 'Lowercase type of "%s" should be used for @%s tag', |
136 | $stackPtr, |
137 | $code, |
138 | [ $type, $annotation ] |
139 | ) || $fix; |
140 | } |
141 | } |
142 | return implode( '|', $typeList ); |
143 | } |
144 | |
145 | /** |
146 | * @param File $phpcsFile |
147 | * @param int $stackPtr |
148 | * @param string $typesString |
149 | * @param bool &$fix Set when autofix is needed |
150 | * @param string $annotation Either "param" or "return" or "var" + "name" or "type" |
151 | * @return string Updated $typesString |
152 | */ |
153 | private function fixTrailingPunctuation( |
154 | File $phpcsFile, |
155 | int $stackPtr, |
156 | string $typesString, |
157 | bool &$fix, |
158 | string $annotation |
159 | ): string { |
160 | if ( preg_match( '/^(.*)((?:(?![\[\]_{}()])\p{P})+)$/', $typesString, $matches ) ) { |
161 | $typesString = $matches[1]; |
162 | $fix = $phpcsFile->addFixableError( |
163 | '%s should not end with punctuation "%s"', |
164 | $stackPtr, |
165 | 'NotPunctuation' . str_replace( ' ', '', ucwords( $annotation ) ), |
166 | [ ucfirst( $annotation ), $matches[2] ] |
167 | ) || $fix; |
168 | } |
169 | return $typesString; |
170 | } |
171 | |
172 | /** |
173 | * @param File $phpcsFile |
174 | * @param int $stackPtr |
175 | * @param string $typesString |
176 | * @param bool &$fix Set when autofix is needed |
177 | * @param string $annotation Either "param" or "return" or "var" + "name" or "type" |
178 | * @return string Updated $typesString |
179 | */ |
180 | private function fixWrappedParenthesis( |
181 | File $phpcsFile, |
182 | int $stackPtr, |
183 | string $typesString, |
184 | bool &$fix, |
185 | string $annotation |
186 | ): string { |
187 | if ( preg_match( '/^([{\[]+)(.*)([\]}]+)$/', $typesString, $matches ) ) { |
188 | $typesString = $matches[2]; |
189 | $fix = $phpcsFile->addFixableError( |
190 | '%s should not be wrapped in parenthesis; %s and %s found', |
191 | $stackPtr, |
192 | 'NotParenthesis' . str_replace( ' ', '', ucwords( $annotation ) ), |
193 | [ ucfirst( $annotation ), $matches[1], $matches[3] ] |
194 | ) || $fix; |
195 | } |
196 | return $typesString; |
197 | } |
198 | |
199 | /** |
200 | * @param File $phpcsFile |
201 | * @param int $stackPtr |
202 | * @param string $typesString |
203 | * @param string $annotation Either "param" or "return" or "var" |
204 | */ |
205 | private function maybeAddObjectTypehintError( |
206 | File $phpcsFile, |
207 | int $stackPtr, |
208 | string $typesString, |
209 | string $annotation |
210 | ): void { |
211 | $typeList = explode( '|', $typesString ); |
212 | foreach ( $typeList as $type ) { |
213 | if ( $type === 'object' || $type === 'object[]' ) { |
214 | $phpcsFile->addWarning( |
215 | '`object` should rarely be used as a typehint. If more specific types are ' . |
216 | 'known, list them. If only plain anonymous objects are expected, use ' . |
217 | '`stdClass`. If the intent is indeed to allow any object, mark it with a ' . |
218 | '// phpcs:… comment or set this rule\'s <severity> to 0.', |
219 | $stackPtr, |
220 | 'ObjectTypeHint' . ucfirst( $annotation ) |
221 | ); |
222 | } |
223 | } |
224 | } |
225 | |
226 | /** |
227 | * Complain about `type` as a type, its likely to have been autogenerated and isn't |
228 | * informative (but we don't care about `Type`, since that might be a class name), |
229 | * see T273806 |
230 | * |
231 | * @param File $phpcsFile |
232 | * @param int $stackPtr |
233 | * @param string $typesString |
234 | * @param string $annotation Either "param" or "return" or "var" |
235 | */ |
236 | private function maybeAddTypeTypehintError( |
237 | File $phpcsFile, |
238 | int $stackPtr, |
239 | string $typesString, |
240 | string $annotation |
241 | ): void { |
242 | $typeList = explode( '|', $typesString ); |
243 | foreach ( $typeList as $type ) { |
244 | if ( $type === 'type' || $type === 'type[]' ) { |
245 | $phpcsFile->addWarning( |
246 | '`type` should not be used as a typehint, the actual type should be used', |
247 | $stackPtr, |
248 | 'TypeTypeHint' . ucfirst( $annotation ) |
249 | ); |
250 | } |
251 | } |
252 | } |
253 | } |