1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Ensures type comments follow basic formatting. |
4
|
|
|
* |
5
|
|
|
* PHP version 5 |
6
|
|
|
* |
7
|
|
|
* @category PHP |
8
|
|
|
* @package PHP_CodeSniffer |
9
|
|
|
* @author Alexander Obuhovich <[email protected]> |
10
|
|
|
* @license https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause |
11
|
|
|
* @link https://github.com/aik099/CodingStandard |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace CodingStandard\Sniffs\Commenting; |
15
|
|
|
|
16
|
|
|
use PHP_CodeSniffer\Files\File; |
17
|
|
|
use PHP_CodeSniffer\Sniffs\Sniff; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Ensures type comments follow basic formatting. |
21
|
|
|
* |
22
|
|
|
* @category PHP |
23
|
|
|
* @package PHP_CodeSniffer |
24
|
|
|
* @author Alexander Obuhovich <[email protected]> |
25
|
|
|
* @license https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause |
26
|
|
|
* @link https://github.com/aik099/CodingStandard |
27
|
|
|
*/ |
28
|
|
|
|
29
|
|
|
class TypeCommentSniff implements Sniff |
30
|
|
|
{ |
31
|
|
|
|
32
|
|
|
const TYPE_TAG = '@var'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* A list of tokenizers this sniff supports. |
36
|
|
|
* |
37
|
|
|
* @var array |
38
|
|
|
*/ |
39
|
|
|
public $supportedTokenizers = array('PHP'); |
40
|
|
|
|
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Returns an array of tokens this test wants to listen for. |
44
|
|
|
* |
45
|
|
|
* @return array |
46
|
|
|
*/ |
47
|
1 |
|
public function register() |
48
|
|
|
{ |
49
|
|
|
return array( |
50
|
1 |
|
T_COMMENT, |
51
|
1 |
|
T_DOC_COMMENT_OPEN_TAG, |
52
|
1 |
|
); |
53
|
|
|
}//end register() |
54
|
|
|
|
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Processes this test, when one of its tokens is encountered. |
58
|
|
|
* |
59
|
|
|
* @param File $phpcsFile The file being scanned. |
60
|
|
|
* @param int $stackPtr The position of the current token in the |
61
|
|
|
* stack passed in $tokens. |
62
|
|
|
* |
63
|
|
|
* @return void |
64
|
|
|
*/ |
65
|
1 |
|
public function process(File $phpcsFile, $stackPtr) |
66
|
|
|
{ |
67
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
68
|
|
|
|
69
|
1 |
|
if ($tokens[$stackPtr]['code'] === T_COMMENT) { |
70
|
1 |
|
$this->processComment($phpcsFile, $stackPtr); |
71
|
1 |
|
} elseif ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) { |
72
|
1 |
|
$this->processDocBlock($phpcsFile, $stackPtr); |
73
|
1 |
|
} |
74
|
1 |
|
}//end process() |
75
|
|
|
|
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Processes comment. |
79
|
|
|
* |
80
|
|
|
* @param File $phpcsFile The file being scanned. |
81
|
|
|
* @param int $stackPtr The position of the current token |
82
|
|
|
* in the stack passed in $tokens. |
83
|
|
|
* |
84
|
|
|
* @return void |
85
|
|
|
*/ |
86
|
1 |
|
public function processComment(File $phpcsFile, $stackPtr) |
87
|
|
|
{ |
88
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
89
|
1 |
|
$commentText = $tokens[$stackPtr]['content']; |
90
|
|
|
|
91
|
|
|
// Multi-line block comment. |
92
|
1 |
|
if (substr($commentText, 0, 2) !== '/*' || substr($commentText, -2) !== '*/') { |
93
|
1 |
|
return; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
// Not a type comment. |
97
|
1 |
|
if ($this->isTypeComment($commentText) === false) { |
98
|
1 |
|
return; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
// The "/**@var ...*/" comment isn't parsed as DocBlock and is fixed here. |
102
|
1 |
|
$error = 'Type comment must be in "/** %s ClassName $variable_name */" format'; |
103
|
1 |
|
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle', array(self::TYPE_TAG)); |
104
|
1 |
|
if ($fix === true) { |
105
|
1 |
|
$phpcsFile->fixer->replaceToken($stackPtr, '/** '.trim($commentText, ' /*').' */'); |
106
|
1 |
|
} |
107
|
1 |
|
}//end processComment() |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Processes DocBlock. |
112
|
|
|
* |
113
|
|
|
* @param File $phpcsFile The file being scanned. |
114
|
|
|
* @param int $stackPtr The position of the current token |
115
|
|
|
* in the stack passed in $tokens. |
116
|
|
|
* |
117
|
|
|
* @return void |
118
|
|
|
*/ |
119
|
1 |
|
public function processDocBlock(File $phpcsFile, $stackPtr) |
120
|
|
|
{ |
121
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
122
|
1 |
|
$commentStart = $stackPtr; |
123
|
1 |
|
$commentEnd = $tokens[$stackPtr]['comment_closer']; |
124
|
|
|
|
125
|
|
|
// Multi-line DocBlock. |
126
|
1 |
|
if ($tokens[$commentEnd]['line'] !== $tokens[$commentStart]['line']) { |
127
|
1 |
|
return; |
128
|
|
|
} |
129
|
|
|
|
130
|
1 |
|
$commentTags = $tokens[$stackPtr]['comment_tags']; |
131
|
|
|
|
132
|
|
|
// Single-line DocBlock without tags inside. |
133
|
1 |
|
if (empty($commentTags) === true) { |
134
|
1 |
|
return; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
// First tag will always exist. |
138
|
1 |
|
$firstTagPtr = $commentTags[0]; |
139
|
|
|
|
140
|
|
|
// Not a type comment. |
141
|
1 |
|
if ($this->isTypeComment($tokens[$firstTagPtr]['content']) === false) { |
142
|
1 |
|
return; |
143
|
|
|
} |
144
|
|
|
|
145
|
1 |
|
$structure = new TypeCommentStructure($phpcsFile, $stackPtr); |
146
|
|
|
|
147
|
1 |
|
if ($structure->className === null) { |
148
|
1 |
|
$error = 'Type comment must be in "/** %s ClassName $variable_name */" format'; |
149
|
1 |
|
$leadingWhitespace = $tokens[$stackPtr + 1]['code'] === T_DOC_COMMENT_WHITESPACE; |
150
|
1 |
|
$trailingWhitespace = $tokens[$commentEnd - 1]['code'] === T_DOC_COMMENT_WHITESPACE; |
151
|
|
|
|
152
|
1 |
|
if ($leadingWhitespace === false || $trailingWhitespace === false) { |
153
|
1 |
|
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle', array(self::TYPE_TAG)); |
154
|
1 |
|
if ($fix === true) { |
155
|
1 |
|
if ($leadingWhitespace === false) { |
156
|
1 |
|
$phpcsFile->fixer->addContentBefore($stackPtr + 1, ' '); |
157
|
1 |
|
} |
158
|
|
|
|
159
|
1 |
|
if ($trailingWhitespace === false) { |
160
|
1 |
|
$phpcsFile->fixer->addContent($commentEnd - 1, ' '); |
161
|
1 |
|
} |
162
|
1 |
|
} |
163
|
1 |
|
} else { |
164
|
1 |
|
$phpcsFile->addError($error, $firstTagPtr, 'WrongStyle', array(self::TYPE_TAG)); |
165
|
|
|
} |
166
|
|
|
|
167
|
1 |
|
return; |
168
|
|
|
}//end if |
169
|
|
|
|
170
|
1 |
|
$this->processDocBlockContent($phpcsFile, $stackPtr, $structure); |
171
|
1 |
|
$this->processVariableAssociation($phpcsFile, $stackPtr, $structure); |
172
|
1 |
|
}//end processDocBlock() |
173
|
|
|
|
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Processes DocBlock content. |
177
|
|
|
* |
178
|
|
|
* @param File $phpcsFile The file being scanned. |
179
|
|
|
* @param int $stackPtr The position of the current token |
180
|
|
|
* in the stack passed in $tokens. |
181
|
|
|
* @param TypeCommentStructure $structure Type comment structure. |
182
|
|
|
* |
183
|
|
|
* @return void |
184
|
|
|
*/ |
185
|
1 |
|
public function processDocBlockContent( |
186
|
|
|
File $phpcsFile, |
187
|
|
|
$stackPtr, |
188
|
|
|
TypeCommentStructure $structure |
189
|
|
|
) { |
190
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
191
|
1 |
|
$commentTags = $tokens[$stackPtr]['comment_tags']; |
192
|
1 |
|
$firstTagPtr = $commentTags[0]; |
193
|
|
|
|
194
|
|
|
// Check correct tag usage. |
195
|
1 |
|
if ($tokens[$firstTagPtr]['content'] !== self::TYPE_TAG) { |
196
|
1 |
|
$fix = $phpcsFile->addFixableError( |
197
|
1 |
|
'Type comment must use "%s" tag; "%s" used', |
198
|
1 |
|
$firstTagPtr, |
199
|
1 |
|
'WrongTag', |
200
|
|
|
array( |
201
|
1 |
|
self::TYPE_TAG, |
202
|
1 |
|
$tokens[$firstTagPtr]['content'], |
203
|
|
|
) |
204
|
1 |
|
); |
205
|
1 |
|
if ($fix === true) { |
206
|
1 |
|
$phpcsFile->fixer->replaceToken($firstTagPtr, self::TYPE_TAG); |
207
|
1 |
|
} |
208
|
1 |
|
} |
209
|
|
|
|
210
|
|
|
// Check spacing around the tag. |
211
|
1 |
|
$spaceBeforeTagPtr = ($stackPtr + 1); |
212
|
1 |
|
if ($tokens[$spaceBeforeTagPtr]['length'] !== 1) { |
213
|
1 |
|
$fix = $phpcsFile->addFixableError( |
214
|
1 |
|
'There must be 1 space before "%s" tag; %d found', |
215
|
1 |
|
$firstTagPtr, |
216
|
1 |
|
'SpaceBeforeTag', |
217
|
|
|
array( |
218
|
1 |
|
$tokens[$firstTagPtr]['content'], |
219
|
1 |
|
$tokens[$spaceBeforeTagPtr]['length'], |
220
|
|
|
) |
221
|
1 |
|
); |
222
|
1 |
|
if ($fix === true) { |
223
|
1 |
|
$phpcsFile->fixer->replaceToken($spaceBeforeTagPtr, ' '); |
224
|
1 |
|
} |
225
|
1 |
|
} |
226
|
|
|
|
227
|
1 |
|
$spaceAfterTagPtr = ($stackPtr + 3); |
228
|
1 |
|
if ($tokens[$spaceAfterTagPtr]['length'] !== 1) { |
229
|
1 |
|
$fix = $phpcsFile->addFixableError( |
230
|
1 |
|
'There must be 1 space between "%s" tag and class name; %d found', |
231
|
1 |
|
$firstTagPtr, |
232
|
1 |
|
'SpaceAfterTag', |
233
|
|
|
array( |
234
|
1 |
|
$tokens[$firstTagPtr]['content'], |
235
|
1 |
|
$tokens[$spaceAfterTagPtr]['length'], |
236
|
|
|
) |
237
|
1 |
|
); |
238
|
1 |
|
if ($fix === true) { |
239
|
1 |
|
$phpcsFile->fixer->replaceToken($spaceAfterTagPtr, ' '); |
240
|
1 |
|
} |
241
|
1 |
|
} |
242
|
|
|
|
243
|
|
|
// Check presence of both type & variable. |
244
|
1 |
|
if ($structure->variableName === null) { |
245
|
1 |
|
if ($structure->isVariable($structure->className) === false) { |
246
|
1 |
|
$phpcsFile->addError( |
247
|
1 |
|
'Type comment missing variable: /** %s %s ______ */', |
248
|
1 |
|
$structure->tagContentPtr, |
249
|
1 |
|
'VariableMissing', |
250
|
|
|
array( |
251
|
1 |
|
self::TYPE_TAG, |
252
|
1 |
|
$structure->className, |
253
|
|
|
) |
254
|
1 |
|
); |
255
|
1 |
|
} else { |
256
|
1 |
|
$phpcsFile->addError( |
257
|
1 |
|
'Type comment missing type: /** %s ______ %s */', |
258
|
1 |
|
$structure->tagContentPtr, |
259
|
1 |
|
'TypeMissing', |
260
|
|
|
array( |
261
|
1 |
|
self::TYPE_TAG, |
262
|
1 |
|
$structure->className, |
263
|
|
|
) |
264
|
1 |
|
); |
265
|
|
|
}//end if |
266
|
|
|
|
267
|
1 |
|
return; |
268
|
|
|
}//end if |
269
|
|
|
|
270
|
1 |
|
$classPositionCorrect = $structure->isVariable($structure->className) === false; |
271
|
1 |
|
$variablePositionCorrect = $structure->isVariable($structure->variableName); |
272
|
|
|
|
273
|
|
|
// Malformed type definition. |
274
|
1 |
|
if (($classPositionCorrect === false && $variablePositionCorrect === true) |
275
|
1 |
|
|| ($classPositionCorrect === true && $variablePositionCorrect === false) |
276
|
1 |
|
) { |
277
|
1 |
|
$error = 'Type comment must be in "/** %s ClassName $variable_name */" format'; |
278
|
1 |
|
$phpcsFile->addError($error, $firstTagPtr, 'WrongStyle', array(self::TYPE_TAG)); |
279
|
|
|
|
280
|
1 |
|
return; |
281
|
|
|
} |
282
|
|
|
|
283
|
1 |
|
if ($classPositionCorrect === true || $variablePositionCorrect === true) { |
284
|
1 |
|
$expectedTagContent = $structure->className.' '.$structure->variableName.' '.$structure->description; |
285
|
1 |
|
} else { |
286
|
1 |
|
$expectedTagContent = $structure->variableName.' '.$structure->className.' '.$structure->description; |
287
|
|
|
} |
288
|
|
|
|
289
|
1 |
|
if ($structure->tagContent !== $expectedTagContent) { |
290
|
1 |
|
$fix = $phpcsFile->addFixableError( |
291
|
1 |
|
'Wrong type and variable spacing/order. Expected: "%s"', |
292
|
1 |
|
$structure->tagContentPtr, |
293
|
1 |
|
'WrongOrder', |
294
|
1 |
|
array($expectedTagContent) |
295
|
1 |
|
); |
296
|
1 |
|
if ($fix === true) { |
297
|
1 |
|
$phpcsFile->fixer->replaceToken($structure->tagContentPtr, $expectedTagContent); |
298
|
1 |
|
} |
299
|
1 |
|
} |
300
|
1 |
|
}//end processDocBlockContent() |
301
|
|
|
|
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Processes variable around DocBlock. |
305
|
|
|
* |
306
|
|
|
* @param File $phpcsFile The file being scanned. |
307
|
|
|
* @param int $stackPtr The position of the current token |
308
|
|
|
* in the stack passed in $tokens. |
309
|
|
|
* @param TypeCommentStructure $structure Type comment structure. |
310
|
|
|
* |
311
|
|
|
* @return void |
312
|
|
|
*/ |
313
|
1 |
|
public function processVariableAssociation( |
314
|
|
|
File $phpcsFile, |
315
|
|
|
$stackPtr, |
316
|
|
|
TypeCommentStructure $structure |
317
|
|
|
) { |
318
|
|
|
// Variable association can't be determined. |
319
|
1 |
|
if ($structure->variableName === null || $structure->isVariable($structure->variableName) === false) { |
320
|
1 |
|
return; |
321
|
|
|
} |
322
|
|
|
|
323
|
1 |
|
$this->processVariableBeforeDocBlock($phpcsFile, $stackPtr, $structure); |
324
|
1 |
|
$this->processVariableAfterDocBlock($phpcsFile, $stackPtr, $structure); |
325
|
1 |
|
}//end processVariableAssociation() |
326
|
|
|
|
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Processes variable before DocBlock. |
330
|
|
|
* |
331
|
|
|
* @param File $phpcsFile The file being scanned. |
332
|
|
|
* @param int $stackPtr The position of the current token |
333
|
|
|
* in the stack passed in $tokens. |
334
|
|
|
* @param TypeCommentStructure $structure Type comment structure. |
335
|
|
|
* |
336
|
|
|
* @return void |
337
|
|
|
*/ |
338
|
1 |
|
public function processVariableBeforeDocBlock( |
339
|
|
|
File $phpcsFile, |
340
|
|
|
$stackPtr, |
341
|
|
|
TypeCommentStructure $structure |
342
|
|
|
) { |
343
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
344
|
|
|
|
345
|
1 |
|
$prevStatementEnd = $phpcsFile->findPrevious( |
346
|
1 |
|
T_WHITESPACE, |
347
|
1 |
|
($stackPtr - 1), |
348
|
1 |
|
null, |
349
|
|
|
true |
350
|
1 |
|
); |
351
|
|
|
|
352
|
|
|
if ($prevStatementEnd === false |
353
|
1 |
|
|| $tokens[$prevStatementEnd]['code'] !== T_SEMICOLON |
354
|
1 |
|
) { |
355
|
1 |
|
return; |
356
|
|
|
} |
357
|
|
|
|
358
|
1 |
|
$assignmentTokenPtr = $phpcsFile->findPrevious( |
359
|
1 |
|
T_EQUAL, |
360
|
1 |
|
($prevStatementEnd - 1), |
361
|
1 |
|
null, |
362
|
1 |
|
false, |
363
|
1 |
|
null, |
364
|
|
|
true |
365
|
1 |
|
); |
366
|
|
|
|
367
|
|
|
// Not an assignment. |
368
|
1 |
|
if ($assignmentTokenPtr === false) { |
369
|
|
|
return; |
370
|
|
|
} |
371
|
|
|
|
372
|
1 |
|
$variableTokenPtr = $phpcsFile->findPrevious( |
373
|
1 |
|
T_WHITESPACE, |
374
|
1 |
|
($assignmentTokenPtr - 1), |
375
|
1 |
|
null, |
376
|
|
|
true |
377
|
1 |
|
); |
378
|
|
|
|
379
|
|
|
// Assignment not to a variable, mentioned in type comment. |
380
|
|
|
if ($variableTokenPtr === false |
381
|
1 |
|
|| $tokens[$variableTokenPtr]['code'] !== T_VARIABLE |
382
|
1 |
|
|| $tokens[$variableTokenPtr]['content'] !== $structure->variableName |
383
|
1 |
|
) { |
384
|
1 |
|
return; |
385
|
|
|
} |
386
|
|
|
|
387
|
1 |
|
$fix = $phpcsFile->addFixableError( |
388
|
1 |
|
'Type comment must be placed before variable declaration', |
389
|
1 |
|
$stackPtr, |
390
|
|
|
'AfterVariable' |
391
|
1 |
|
); |
392
|
1 |
|
if ($fix === true) { |
393
|
1 |
|
$move_content = ''; |
394
|
1 |
|
$copyStartPtr = $this->findFirstOnLine($phpcsFile, $stackPtr); |
395
|
1 |
|
$copyEndPtr = $this->findLastOnLine($phpcsFile, $stackPtr); |
396
|
|
|
|
397
|
1 |
|
$phpcsFile->fixer->beginChangeset(); |
398
|
|
|
|
399
|
1 |
|
for ($i = $copyStartPtr; $i <= $copyEndPtr; $i++) { |
400
|
1 |
|
$move_content .= $phpcsFile->fixer->getTokenContent($i); |
|
|
|
|
401
|
1 |
|
$phpcsFile->fixer->replaceToken($i, ''); |
|
|
|
|
402
|
1 |
|
} |
403
|
|
|
|
404
|
1 |
|
$phpcsFile->fixer->addContentBefore( |
405
|
1 |
|
$this->findFirstOnLine($phpcsFile, $variableTokenPtr), |
|
|
|
|
406
|
|
|
$move_content |
407
|
1 |
|
); |
408
|
1 |
|
$phpcsFile->fixer->endChangeset(); |
409
|
1 |
|
}//end if |
410
|
1 |
|
}//end processVariableBeforeDocBlock() |
411
|
|
|
|
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Processes variable before DocBlock. |
415
|
|
|
* |
416
|
|
|
* @param File $phpcsFile The file being scanned. |
417
|
|
|
* @param int $stackPtr The position of the current token |
418
|
|
|
* in the stack passed in $tokens. |
419
|
|
|
* @param TypeCommentStructure $structure Type comment structure. |
420
|
|
|
* |
421
|
|
|
* @return void |
422
|
|
|
*/ |
423
|
1 |
|
public function processVariableAfterDocBlock( |
424
|
|
|
File $phpcsFile, |
425
|
|
|
$stackPtr, |
426
|
|
|
TypeCommentStructure $structure |
427
|
|
|
) { |
428
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
429
|
1 |
|
$variableTokenPtr = $phpcsFile->findNext( |
430
|
1 |
|
T_WHITESPACE, |
431
|
1 |
|
($tokens[$stackPtr]['comment_closer'] + 1), |
432
|
1 |
|
null, |
433
|
|
|
true |
434
|
1 |
|
); |
435
|
|
|
|
436
|
|
|
// No variable placed on next line after type comment. |
437
|
|
|
if ($variableTokenPtr === false |
438
|
1 |
|
|| $tokens[$variableTokenPtr]['code'] !== T_VARIABLE |
439
|
1 |
|
|| $tokens[$variableTokenPtr]['line'] !== ($tokens[$stackPtr]['line'] + 1) |
440
|
1 |
|
) { |
441
|
1 |
|
return; |
442
|
|
|
} |
443
|
|
|
|
444
|
1 |
|
if ($tokens[$variableTokenPtr]['content'] !== $structure->variableName) { |
445
|
1 |
|
$phpcsFile->addError( |
446
|
1 |
|
'Type comment variable mismatch, expected "%s"; found: "%s"', |
447
|
1 |
|
$structure->tagContentPtr, |
448
|
1 |
|
'VariableMismatch', |
449
|
|
|
array( |
450
|
1 |
|
$tokens[$variableTokenPtr]['content'], |
451
|
1 |
|
$structure->variableName, |
452
|
|
|
) |
453
|
1 |
|
); |
454
|
|
|
|
455
|
|
|
// Don't apply more checks, unless type comment is in sync with variable. |
456
|
1 |
|
return; |
457
|
|
|
} |
458
|
|
|
|
459
|
1 |
|
$prevStatementEnd = $phpcsFile->findPrevious( |
460
|
1 |
|
T_WHITESPACE, |
461
|
1 |
|
($stackPtr - 1), |
462
|
1 |
|
null, |
463
|
|
|
true |
464
|
1 |
|
); |
465
|
|
|
|
466
|
|
|
// Previous statement is absent or placed correctly. |
467
|
|
|
if ($prevStatementEnd === false |
468
|
1 |
|
|| $tokens[$prevStatementEnd]['code'] !== T_SEMICOLON |
469
|
1 |
|
|| $tokens[$prevStatementEnd]['line'] < ($tokens[$stackPtr]['line'] - 1) |
470
|
1 |
|
) { |
471
|
1 |
|
return; |
472
|
|
|
} |
473
|
|
|
|
474
|
1 |
|
$fix = $phpcsFile->addFixableError( |
475
|
1 |
|
'There must be at least 1 empty line above type comment', |
476
|
1 |
|
$stackPtr, |
477
|
|
|
'EmptyLineAbove' |
478
|
1 |
|
); |
479
|
1 |
|
if ($fix === true) { |
480
|
1 |
|
$phpcsFile->fixer->addNewline($prevStatementEnd); |
|
|
|
|
481
|
1 |
|
} |
482
|
1 |
|
}//end processVariableAfterDocBlock() |
483
|
|
|
|
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* Checks if comment is type comment. |
487
|
|
|
* |
488
|
|
|
* @param string $commentText Comment text. |
489
|
|
|
* |
490
|
|
|
* @return bool |
491
|
|
|
*/ |
492
|
1 |
|
protected function isTypeComment($commentText) |
493
|
|
|
{ |
494
|
1 |
|
return strpos($commentText, self::TYPE_TAG) !== false || strpos($commentText, '@type') !== false; |
495
|
|
|
}//end isTypeComment() |
496
|
|
|
|
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* Finds first token on a line. |
500
|
|
|
* |
501
|
|
|
* @param File $phpcsFile All the tokens found in the document. |
502
|
|
|
* @param int $start Start from token. |
503
|
|
|
* |
504
|
|
|
* @return int | bool |
505
|
|
|
*/ |
506
|
1 |
|
public function findFirstOnLine(File $phpcsFile, $start) |
507
|
|
|
{ |
508
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
509
|
|
|
|
510
|
1 |
|
for ($i = $start; $i >= 0; $i--) { |
511
|
1 |
|
if ($tokens[$i]['line'] === $tokens[$start]['line']) { |
512
|
1 |
|
continue; |
513
|
|
|
} |
514
|
|
|
|
515
|
1 |
|
return ($i + 1); |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
return false; |
519
|
|
|
}//end findFirstOnLine() |
520
|
|
|
|
521
|
|
|
|
522
|
|
|
/** |
523
|
|
|
* Finds last token on a line. |
524
|
|
|
* |
525
|
|
|
* @param File $phpcsFile All the tokens found in the document. |
526
|
|
|
* @param int $start Start from token. |
527
|
|
|
* |
528
|
|
|
* @return int | bool |
529
|
|
|
*/ |
530
|
1 |
|
public function findLastOnLine(File $phpcsFile, $start) |
531
|
|
|
{ |
532
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
533
|
|
|
|
534
|
1 |
|
for ($i = $start; $i <= $phpcsFile->numTokens; $i++) { |
535
|
1 |
|
if ($tokens[$i]['line'] === $tokens[$start]['line']) { |
536
|
1 |
|
continue; |
537
|
|
|
} |
538
|
|
|
|
539
|
1 |
|
return ($i - 1); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
return false; |
543
|
|
|
}//end findLastOnLine() |
544
|
|
|
}//end class |
545
|
|
|
|
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Type comment structure |
549
|
|
|
*/ |
550
|
|
|
class TypeCommentStructure |
|
|
|
|
551
|
|
|
{ |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* Tag content pointer. |
555
|
|
|
* |
556
|
|
|
* @var int |
557
|
|
|
*/ |
558
|
|
|
public $tagContentPtr; |
559
|
|
|
|
560
|
|
|
/** |
561
|
|
|
* Tag content. |
562
|
|
|
* |
563
|
|
|
* @var string |
564
|
|
|
*/ |
565
|
|
|
public $tagContent; |
566
|
|
|
|
567
|
|
|
/** |
568
|
|
|
* Class name. |
569
|
|
|
* |
570
|
|
|
* @var string |
571
|
|
|
*/ |
572
|
|
|
public $className; |
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* Variable name. |
576
|
|
|
* |
577
|
|
|
* @var string |
578
|
|
|
*/ |
579
|
|
|
public $variableName; |
580
|
|
|
|
581
|
|
|
/** |
582
|
|
|
* Description. |
583
|
|
|
* |
584
|
|
|
* @var string |
585
|
|
|
*/ |
586
|
|
|
public $description; |
587
|
|
|
|
588
|
|
|
/** |
589
|
|
|
* Token sequence. |
590
|
|
|
* |
591
|
|
|
* @var array |
592
|
|
|
*/ |
593
|
|
|
protected $tokenSequence = array( |
594
|
|
|
1 => T_DOC_COMMENT_WHITESPACE, |
595
|
|
|
2 => T_DOC_COMMENT_TAG, |
596
|
|
|
3 => T_DOC_COMMENT_WHITESPACE, |
597
|
|
|
4 => T_DOC_COMMENT_STRING, |
598
|
|
|
); |
599
|
|
|
|
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Creates from tokens. |
603
|
|
|
* |
604
|
|
|
* @param File $phpcsFile The file being scanned. |
605
|
|
|
* @param int $stackPtr The position of the current token |
606
|
|
|
* in the stack passed in $tokens. |
607
|
|
|
*/ |
608
|
1 |
|
public function __construct(File $phpcsFile, $stackPtr) |
609
|
|
|
{ |
610
|
1 |
|
$tokens = $phpcsFile->getTokens(); |
611
|
|
|
|
612
|
|
|
// Ensure, that DocBlock is built from correct tokens. |
613
|
1 |
|
for ($i = 1; $i <= 4; $i++) { |
614
|
1 |
|
if ($tokens[($stackPtr + $i)]['code'] !== $this->tokenSequence[$i]) { |
615
|
1 |
|
return; |
616
|
|
|
} |
617
|
1 |
|
} |
618
|
|
|
|
619
|
1 |
|
$this->tagContentPtr = ($stackPtr + 4); |
620
|
1 |
|
$this->tagContent = $tokens[$this->tagContentPtr]['content']; |
621
|
1 |
|
$tagContentParts = array_values(array_filter(explode(' ', $this->tagContent))); |
622
|
|
|
|
623
|
1 |
|
if (isset($tagContentParts[0]) === true) { |
624
|
1 |
|
$this->className = $tagContentParts[0]; |
625
|
1 |
|
} |
626
|
|
|
|
627
|
1 |
|
if (isset($tagContentParts[1]) === true) { |
628
|
1 |
|
$this->variableName = $tagContentParts[1]; |
629
|
1 |
|
} |
630
|
|
|
|
631
|
1 |
|
if (count($tagContentParts) > 2) { |
632
|
1 |
|
$this->description = implode(' ', array_slice($tagContentParts, 2)).' '; |
633
|
1 |
|
} else { |
634
|
1 |
|
$this->description = ''; |
635
|
|
|
} |
636
|
1 |
|
}//end __construct() |
637
|
|
|
|
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Detects if given text is a variable. |
641
|
|
|
* |
642
|
|
|
* @param string $text Text. |
643
|
|
|
* |
644
|
|
|
* @return bool |
645
|
|
|
*/ |
646
|
1 |
|
public function isVariable($text) |
647
|
|
|
{ |
648
|
1 |
|
return substr($text, 0, 1) === '$'; |
649
|
|
|
}//end isVariable() |
650
|
|
|
}//end class |
651
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.