1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace BestIt\Sniffs\Commenting; |
6
|
|
|
|
7
|
|
|
use PHP_CodeSniffer_File; |
8
|
|
|
use PHP_CodeSniffer_Sniff; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* This Sniff checks the method phpdoc |
12
|
|
|
* |
13
|
|
|
* It is a modified version of the Generic DocCommentSniff adjusted for the bestit codestyle |
14
|
|
|
* |
15
|
|
|
* @package BestIt\Sniffs\Commenting |
16
|
|
|
* @author Nils Hardeweg <[email protected]> |
17
|
|
|
*/ |
18
|
|
|
class MethodDocSniff implements PHP_CodeSniffer_Sniff |
19
|
|
|
{ |
20
|
|
|
#region constants |
21
|
|
|
/** |
22
|
|
|
* Code for an empty doc block |
23
|
|
|
* |
24
|
|
|
* @var string |
25
|
|
|
*/ |
26
|
|
|
const CODE_EMPTY = 'Empty'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Message for an empty doc block |
30
|
|
|
* |
31
|
|
|
* @var string |
32
|
|
|
*/ |
33
|
|
|
const MESSAGE_DOC_EMPTY = 'Doc comment is empty'; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Code for a Spacing Error after the Summary |
37
|
|
|
* |
38
|
|
|
* @var string |
39
|
|
|
*/ |
40
|
|
|
const CODE_SPACING_AFTER = 'SpacingAfter'; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Message for a Spacing Error after the Summary |
44
|
|
|
* |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
const MESSAGE_SPACING_AFTER = 'Additional blank lines found at end of doc comment'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Code for missing spacing between descriptions |
51
|
|
|
* |
52
|
|
|
* @var string |
53
|
|
|
*/ |
54
|
|
|
const CODE_SPACING_BETWEEN = 'SpacingBetween'; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Message for missing spacing between descriptions |
58
|
|
|
* |
59
|
|
|
* @var string |
60
|
|
|
*/ |
61
|
|
|
const MESSAGE_SPACING_BETWEEN = 'There must be exactly one blank line between descriptions in a doc comment'; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Code for missing spacing before the tags |
65
|
|
|
* |
66
|
|
|
* @var string |
67
|
|
|
*/ |
68
|
|
|
const CODE_SPACING_BEFORE_TAGS = 'SpacingBeforeTags'; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Message for missing spacing before the tags |
72
|
|
|
* |
73
|
|
|
* @var string |
74
|
|
|
*/ |
75
|
|
|
const MESSAGE_SPACING_BEFORE_TAGS = 'There must be exactly one blank line before the tags in a doc comment'; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Code for missing short description (Summary) |
79
|
|
|
* |
80
|
|
|
* @var string |
81
|
|
|
*/ |
82
|
|
|
const CODE_MISSING_SHORT = 'MissingShort'; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Message for missing short description (Summary) |
86
|
|
|
* |
87
|
|
|
* @var string |
88
|
|
|
*/ |
89
|
|
|
const MESSAGE_MISSING_SHORT = 'Missing short description in doc comment'; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Code for not capitalized short description |
93
|
|
|
* |
94
|
|
|
* @var string |
95
|
|
|
*/ |
96
|
|
|
const CODE_SHORT_NOT_CAPITAL = 'ShortNotCapital'; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Message for not capitalized short description |
100
|
|
|
* |
101
|
|
|
* @var string |
102
|
|
|
*/ |
103
|
|
|
const MESSAGE_SHORT_NOT_CAPITAL = 'Doc comment short description must start with a capital letter'; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Code for not Capitalized long description |
107
|
|
|
* |
108
|
|
|
* @var string |
109
|
|
|
*/ |
110
|
|
|
const CODE_LONG_NOT_CAPITAL = 'LongNotCapital'; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Message for not Capitalized long description |
114
|
|
|
* |
115
|
|
|
* @var string |
116
|
|
|
*/ |
117
|
|
|
const MESSAGE_LONG_NOT_CAPITAL = 'Doc comment long description must start with a capital letter'; |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Code for empty lines before the short description |
121
|
|
|
* |
122
|
|
|
* @var string |
123
|
|
|
*/ |
124
|
|
|
const CODE_SPACING_BEFORE_SHORT = 'SpacingBeforeShort'; |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Message for empty lines before the short description |
128
|
|
|
* |
129
|
|
|
* @var string |
130
|
|
|
*/ |
131
|
|
|
const MESSAGE_CODE_SPACING_BEFORE = 'Doc comment short description must be on the first line'; |
132
|
|
|
#endregion |
133
|
|
|
#region Properties |
134
|
|
|
/** |
135
|
|
|
* A list of tokenizers this sniff supports. |
136
|
|
|
* |
137
|
|
|
* @var array |
138
|
|
|
*/ |
139
|
|
|
public $supportedTokenizers = ['PHP', 'JS']; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* The token array is the base of this evaluation |
143
|
|
|
* |
144
|
|
|
* @var array |
145
|
|
|
*/ |
146
|
|
|
public $tokens; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Definitions for an empty doc block |
150
|
|
|
* |
151
|
|
|
* @var array |
152
|
|
|
*/ |
153
|
|
|
public $empty = [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR]; |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* The phpcs file |
157
|
|
|
* |
158
|
|
|
* @var PHP_CodeSniffer_File |
159
|
|
|
*/ |
160
|
|
|
public $phpcsFile; |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Pointer to the start of the comment |
164
|
|
|
* |
165
|
|
|
* @var int |
166
|
|
|
*/ |
167
|
|
|
public $commentStart; |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Pointer to the start of the short description |
171
|
|
|
* |
172
|
|
|
* @var int |
173
|
|
|
*/ |
174
|
|
|
public $short; |
175
|
|
|
#endregion |
176
|
|
|
/** |
177
|
|
|
* Returns an array of tokens this test wants to listen for. |
178
|
|
|
* |
179
|
|
|
* @return array |
180
|
|
|
*/ |
181
|
8 |
|
public function register() |
182
|
|
|
{ |
183
|
8 |
|
return [T_DOC_COMMENT_OPEN_TAG]; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Processes this test, when one of its tokens is encountered. |
188
|
|
|
* |
189
|
|
|
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
190
|
|
|
* @param int $stackPtr The position of the current token in the stack passed in $tokens. |
191
|
|
|
* |
192
|
|
|
* @return void |
193
|
|
|
*/ |
194
|
8 |
|
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
195
|
|
|
{ |
196
|
8 |
|
$this->tokens = $phpcsFile->getTokens(); |
197
|
8 |
|
$this->phpcsFile = $phpcsFile; |
198
|
|
|
|
199
|
8 |
|
$this->commentStart = $stackPtr; |
200
|
8 |
|
$commentEnd = $this->tokens[$stackPtr]['comment_closer']; |
201
|
|
|
|
202
|
8 |
|
$this->short = $this->phpcsFile->findNext($this->empty, $stackPtr + 1, $commentEnd, true); |
|
|
|
|
203
|
|
|
|
204
|
8 |
|
if ($this->short === false) { |
205
|
|
|
// No content at all. |
206
|
1 |
|
$phpcsFile->addError(self::MESSAGE_DOC_EMPTY, $stackPtr, self::CODE_EMPTY); |
207
|
1 |
|
return; |
208
|
|
|
} |
209
|
7 |
|
$this->checkMethodPhpDoc( |
210
|
|
|
$stackPtr, |
211
|
|
|
$commentEnd |
212
|
|
|
); |
213
|
7 |
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Checks if there is a blank line at the end of the doc |
217
|
|
|
* |
218
|
|
|
* @param $commentEnd |
219
|
|
|
* @param $prev |
220
|
|
|
* |
221
|
|
|
* @return void |
222
|
|
|
*/ |
223
|
7 |
|
private function checkBlankLines($commentEnd, $prev) |
224
|
|
|
{ |
225
|
|
|
// Check for additional blank lines at the end of the comment. |
226
|
7 |
View Code Duplication |
if ($this->tokens[$prev]['line'] < ($this->tokens[$commentEnd]['line'] - 1)) { |
|
|
|
|
227
|
|
|
$this->phpcsFile->addError(self::MESSAGE_SPACING_AFTER, $commentEnd, self::CODE_SPACING_AFTER); |
228
|
|
|
} |
229
|
7 |
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Checks if short or long description starts with a capital letter |
233
|
|
|
* |
234
|
|
|
* @param $shortContent |
235
|
|
|
* @param $long |
236
|
|
|
* |
237
|
|
|
* @return void |
238
|
|
|
*/ |
239
|
6 |
|
private function checkCapitalLetter($shortContent, $long) |
240
|
|
|
{ |
241
|
6 |
View Code Duplication |
if (preg_match('/^\p{Ll}/u', $shortContent) === 1) { |
|
|
|
|
242
|
1 |
|
$this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL); |
243
|
|
|
} |
244
|
|
|
|
245
|
6 |
|
if ($long !== false |
246
|
6 |
|
&& $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING && preg_match(' |
247
|
6 |
|
/^\p{Ll}/u', $this->tokens[$long]['content']) === 1 |
248
|
|
|
) { |
249
|
1 |
|
$this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL); |
250
|
|
|
} |
251
|
6 |
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Checks capital letter and spacing of long description |
255
|
|
|
* |
256
|
|
|
* @param $shortEnd |
257
|
|
|
* @param $long |
258
|
|
|
* |
259
|
|
|
* @return void |
260
|
|
|
*/ |
261
|
6 |
|
private function checkLongDescription( |
262
|
|
|
$shortEnd, |
263
|
|
|
$long |
264
|
|
|
) { |
265
|
6 |
|
if ($long !== false && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING) { |
266
|
2 |
View Code Duplication |
if ($this->tokens[$long]['line'] !== ($this->tokens[$shortEnd]['line'] + 2)) { |
|
|
|
|
267
|
1 |
|
$this->phpcsFile->addError(self::MESSAGE_SPACING_BETWEEN, $long, self::CODE_SPACING_BETWEEN); |
268
|
|
|
} |
269
|
|
|
|
270
|
2 |
|
if (preg_match('/^\p{Ll}/u', $this->tokens[$long]['content']) === 1) { |
271
|
1 |
|
$this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL); |
272
|
|
|
} |
273
|
|
|
} |
274
|
6 |
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Checks for a blank line before the doc tags |
278
|
|
|
* |
279
|
|
|
* @param $stackPtr |
280
|
|
|
* |
281
|
|
|
* @return void |
282
|
|
|
*/ |
283
|
6 |
|
private function checkCommentTags($stackPtr) |
284
|
|
|
{ |
285
|
6 |
|
if (!empty($this->tokens[$this->commentStart]['comment_tags'])) { |
286
|
6 |
|
$firstTag = $this->tokens[$this->commentStart]['comment_tags'][0]; |
287
|
6 |
|
$prev = $this->phpcsFile->findPrevious($this->empty, $firstTag - 1, $stackPtr, true); |
288
|
|
|
|
289
|
6 |
|
if ($this->tokens[$firstTag]['line'] !== ($this->tokens[$prev]['line'] + 2) |
290
|
6 |
|
&& $this->tokens[$this->short]['content'] !== '@inheritdoc' |
291
|
6 |
|
&& $this->tokens[$this->short]['content'] !== '@var' |
292
|
|
|
) { |
293
|
|
|
$this |
294
|
1 |
|
->phpcsFile |
295
|
1 |
|
->addError(self::MESSAGE_SPACING_BEFORE_TAGS, $firstTag, self::CODE_SPACING_BEFORE_TAGS); |
296
|
|
|
} |
297
|
|
|
} |
298
|
6 |
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Checks long and short description |
302
|
|
|
* |
303
|
|
|
* @param $stackPtr |
304
|
|
|
* @param $commentEnd |
305
|
|
|
* |
306
|
|
|
* @return void |
307
|
|
|
*/ |
308
|
6 |
|
private function checkSummaryAndLongDescription($stackPtr, $commentEnd) |
309
|
|
|
{ |
310
|
|
|
// No extra newline before short description. |
311
|
6 |
|
if ($this->tokens[$this->short]['line'] !== ($this->tokens[$stackPtr]['line'] + 1) |
312
|
6 |
|
&& $this->tokens[$this->short]['content'] !== '@var') { |
313
|
1 |
|
$this->phpcsFile->addError( |
314
|
1 |
|
self::MESSAGE_CODE_SPACING_BEFORE, |
315
|
1 |
|
$this->short, |
316
|
1 |
|
self::CODE_SPACING_BEFORE_SHORT |
317
|
|
|
); |
318
|
|
|
} |
319
|
|
|
|
320
|
6 |
|
$shortContent = $this->tokens[$this->short]['content']; |
321
|
|
|
/** @var int $shortEnd */ |
322
|
6 |
|
$shortEnd = $this->short; |
323
|
|
|
|
324
|
6 |
View Code Duplication |
if (preg_match('/^\p{Ll}/u', $shortContent) === 1) { |
|
|
|
|
325
|
1 |
|
$this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL); |
326
|
|
|
} |
327
|
|
|
|
328
|
6 |
|
$long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true); |
329
|
|
|
|
330
|
6 |
|
$this->checkCapitalLetter($shortContent, $long); |
331
|
|
|
|
332
|
6 |
|
$long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true); |
333
|
|
|
|
334
|
6 |
|
$this->checkLongDescription($shortEnd, $long); |
335
|
|
|
|
336
|
6 |
|
$this->checkCommentTags($stackPtr); |
337
|
6 |
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Checks the method doc block |
341
|
|
|
* |
342
|
|
|
* @param $stackPtr |
343
|
|
|
* @param $commentEnd |
344
|
|
|
* |
345
|
|
|
* @return void |
346
|
|
|
*/ |
347
|
7 |
|
private function checkMethodPhpDoc($stackPtr, $commentEnd) |
348
|
|
|
{ |
349
|
|
|
// The last line of the comment should just be the */ code. |
350
|
7 |
|
$prev = $this->phpcsFile->findPrevious($this->empty, $commentEnd - 1, $stackPtr, true); |
351
|
|
|
|
352
|
7 |
|
$this->checkBlankLines($commentEnd, $prev); |
353
|
|
|
|
354
|
|
|
// Check for a comment description. |
355
|
7 |
|
if ($this->tokens[$this->short]['code'] !== T_DOC_COMMENT_STRING |
356
|
7 |
|
&& $this->tokens[$this->short]['content'] !== '@inheritdoc' |
357
|
7 |
|
&& $this->tokens[$this->short]['content'] !== '@var' |
358
|
|
|
) { |
359
|
1 |
|
$error = self::MESSAGE_MISSING_SHORT; |
360
|
1 |
|
$this->phpcsFile->addError($error, $stackPtr, self::CODE_MISSING_SHORT); |
361
|
1 |
|
return; |
362
|
|
|
} |
363
|
6 |
|
$this->checkSummaryAndLongDescription($stackPtr, $commentEnd); |
364
|
6 |
|
} |
365
|
|
|
} |
366
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.