1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
|
6
|
|
|
namespace BestIt\Sniffs\Commenting; |
7
|
|
|
|
8
|
|
|
use PHP_CodeSniffer_File; |
9
|
|
|
use PHP_CodeSniffer_Sniff; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* This Sniff checks the method phpdoc |
13
|
|
|
* |
14
|
|
|
* It is a modified version of the Generic DocCommentSniff adjusted for the bestit codestyle |
15
|
|
|
* |
16
|
|
|
* @package BestIt\Sniffs\Commenting |
17
|
|
|
*/ |
18
|
|
|
class MethodDocSniff implements PHP_CodeSniffer_Sniff |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* Code for an empty doc block |
22
|
|
|
* |
23
|
|
|
* @var string |
24
|
|
|
*/ |
25
|
|
|
const CODE_EMPTY = 'Empty'; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Code for missing spacing between descriptions |
29
|
|
|
* |
30
|
|
|
* @var string |
31
|
|
|
*/ |
32
|
|
|
const CODE_SPACING_BETWEEN = 'SpacingBetween'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Code for missing spacing before the tags |
36
|
|
|
* |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
const CODE_SPACING_BEFORE_TAGS = 'SpacingBeforeTags'; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Code for missing short description (Summary) |
43
|
|
|
* |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
const CODE_MISSING_SHORT = 'MissingShort'; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Code for not capitalized short description |
50
|
|
|
* |
51
|
|
|
* @var string |
52
|
|
|
*/ |
53
|
|
|
const CODE_SHORT_NOT_CAPITAL = 'ShortNotCapital'; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Code for not Capitalized long description |
57
|
|
|
* |
58
|
|
|
* @var string |
59
|
|
|
*/ |
60
|
|
|
const CODE_LONG_NOT_CAPITAL = 'LongNotCapital'; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Code for empty lines before the short description |
64
|
|
|
* |
65
|
|
|
* @var string |
66
|
|
|
*/ |
67
|
|
|
const CODE_SPACING_BEFORE_SHORT = 'SpacingBeforeShort'; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* A list of tokenizers this sniff supports. |
71
|
|
|
* |
72
|
|
|
* @var array |
73
|
|
|
*/ |
74
|
|
|
public $supportedTokenizers = ['PHP', 'JS']; |
75
|
|
|
|
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Returns an array of tokens this test wants to listen for. |
79
|
|
|
* |
80
|
|
|
* @return array |
81
|
|
|
*/ |
82
|
6 |
|
public function register() |
83
|
|
|
{ |
84
|
6 |
|
return [T_DOC_COMMENT_OPEN_TAG]; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Processes this test, when one of its tokens is encountered. |
89
|
|
|
* |
90
|
|
|
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
91
|
|
|
* @param int $stackPtr The position of the current token in the stack passed in $tokens. |
92
|
|
|
* |
93
|
|
|
* @return void |
94
|
|
|
*/ |
95
|
6 |
|
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
96
|
|
|
{ |
97
|
6 |
|
$tokens = $phpcsFile->getTokens(); |
98
|
6 |
|
$commentStart = $stackPtr; |
99
|
6 |
|
$commentEnd = $tokens[$stackPtr]['comment_closer']; |
100
|
|
|
|
101
|
6 |
|
$empty = [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR]; |
102
|
|
|
|
103
|
6 |
|
$short = $phpcsFile->findNext($empty, $stackPtr + 1, $commentEnd, true); |
104
|
6 |
|
if ($short === false) { |
105
|
|
|
// No content at all. |
106
|
1 |
|
$error = 'Doc comment is empty'; |
107
|
1 |
|
$phpcsFile->addError($error, $stackPtr, self::CODE_EMPTY); |
108
|
1 |
|
return; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
// The last line of the comment should just be the */ code. |
112
|
5 |
|
$prev = $phpcsFile->findPrevious($empty, $commentEnd - 1, $stackPtr, true); |
113
|
|
|
|
114
|
|
|
// Check for additional blank lines at the end of the comment. |
115
|
5 |
|
if ($tokens[$prev]['line'] < ($tokens[$commentEnd]['line'] - 1)) { |
116
|
|
|
$error = 'Additional blank lines found at end of doc comment'; |
117
|
|
|
$fix = $phpcsFile->addFixableError($error, $commentEnd, 'SpacingAfter'); |
118
|
|
|
if ($fix === true) { |
119
|
|
|
$phpcsFile->fixer->beginChangeset(); |
120
|
|
View Code Duplication |
for ($i = ($prev + 1); $i < $commentEnd; $i++) { |
|
|
|
|
121
|
|
|
if ($tokens[$i + 1]['line'] === $tokens[$commentEnd]['line']) { |
122
|
|
|
break; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
$phpcsFile->fixer->endChangeset(); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
// Check for a comment description. |
133
|
5 |
|
if ($tokens[$short]['code'] !== T_DOC_COMMENT_STRING && $tokens[$short]['content'] !== '@inheritdoc') { |
134
|
1 |
|
$error = 'Missing short description in doc comment'; |
135
|
1 |
|
$phpcsFile->addError($error, $stackPtr, self::CODE_MISSING_SHORT); |
136
|
1 |
|
return; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
// No extra newline before short description. |
140
|
4 |
View Code Duplication |
if ($tokens[$short]['line'] !== ($tokens[$stackPtr]['line'] + 1)) { |
|
|
|
|
141
|
1 |
|
$error = 'Doc comment short description must be on the first line'; |
142
|
1 |
|
$fix = $phpcsFile->addFixableError($error, $short, self::CODE_SPACING_BEFORE_SHORT); |
143
|
1 |
|
if ($fix === true) { |
144
|
|
|
$phpcsFile->fixer->beginChangeset(); |
145
|
|
|
for ($i = $stackPtr; $i < $short; $i++) { |
146
|
|
|
if ($tokens[$i]['line'] === $tokens[$stackPtr]['line']) { |
147
|
|
|
continue; |
148
|
|
|
} elseif ($tokens[$i]['line'] === $tokens[$short]['line']) { |
149
|
|
|
break; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$phpcsFile->fixer->endChangeset(); |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
4 |
|
$shortContent = $tokens[$short]['content']; |
160
|
4 |
|
$shortEnd = $short; |
161
|
|
|
|
162
|
4 |
|
if (preg_match('/^\p{Ll}/u', $shortContent) === 1) { |
163
|
1 |
|
$error = 'Doc comment short description must start with a capital letter'; |
164
|
1 |
|
$phpcsFile->addError($error, $short, self::CODE_SHORT_NOT_CAPITAL); |
165
|
|
|
} |
166
|
|
|
|
167
|
4 |
|
$long = $phpcsFile->findNext($empty, $shortEnd + 1, $commentEnd - 1, true); |
168
|
4 |
|
if ($long !== false && $tokens[$long]['code'] === T_DOC_COMMENT_STRING) { |
169
|
2 |
View Code Duplication |
if (preg_match('/^\p{Ll}/u', $tokens[$long]['content']) === 1) { |
|
|
|
|
170
|
1 |
|
$error = 'Doc comment long description must start with a capital letter'; |
171
|
1 |
|
$phpcsFile->addError($error, $long, self::CODE_LONG_NOT_CAPITAL); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
4 |
|
$long = $phpcsFile->findNext($empty, $shortEnd + 1, $commentEnd - 1, true); |
176
|
4 |
|
if ($long !== false && $tokens[$long]['code'] === T_DOC_COMMENT_STRING) { |
177
|
2 |
View Code Duplication |
if ($tokens[$long]['line'] !== ($tokens[$shortEnd]['line'] + 2)) { |
|
|
|
|
178
|
1 |
|
$error = 'There must be exactly one blank line between descriptions in a doc comment'; |
179
|
1 |
|
$fix = $phpcsFile->addFixableError($error, $long, self::CODE_SPACING_BETWEEN); |
180
|
1 |
|
if ($fix === true) { |
181
|
|
|
$phpcsFile->fixer->beginChangeset(); |
182
|
|
|
for ($i = ($shortEnd + 1); $i < $long; $i++) { |
183
|
|
|
if ($tokens[$i]['line'] === $tokens[$shortEnd]['line']) { |
184
|
|
|
continue; |
185
|
|
|
} elseif ($tokens[$i]['line'] === ($tokens[$long]['line'] - 1)) { |
186
|
|
|
break; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
$phpcsFile->fixer->endChangeset(); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
2 |
View Code Duplication |
if (preg_match('/^\p{Ll}/u', $tokens[$long]['content']) === 1) { |
|
|
|
|
197
|
1 |
|
$error = 'Doc comment long description must start with a capital letter'; |
198
|
1 |
|
$phpcsFile->addError($error, $long, self::CODE_LONG_NOT_CAPITAL); |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
202
|
4 |
|
if (empty($tokens[$commentStart]['comment_tags']) === true) { |
203
|
|
|
// No tags in the comment. |
204
|
1 |
|
return; |
205
|
|
|
} |
206
|
|
|
|
207
|
4 |
|
$firstTag = $tokens[$commentStart]['comment_tags'][0]; |
208
|
4 |
|
$prev = $phpcsFile->findPrevious($empty, $firstTag - 1, $stackPtr, true); |
209
|
4 |
|
if ($tokens[$firstTag]['line'] !== ($tokens[$prev]['line'] + 2) |
210
|
4 |
|
&& $tokens[$short]['content'] !== '@inheritdoc' |
211
|
|
|
) { |
212
|
1 |
|
$error = 'There must be exactly one blank line before the tags in a doc comment'; |
213
|
1 |
|
$fix = $phpcsFile->addFixableError($error, $firstTag, self::CODE_SPACING_BEFORE_TAGS); |
214
|
1 |
|
if ($fix === true) { |
215
|
|
|
$phpcsFile->fixer->beginChangeset(); |
216
|
|
View Code Duplication |
for ($i = ($prev + 1); $i < $firstTag; $i++) { |
|
|
|
|
217
|
|
|
if ($tokens[$i]['line'] === $tokens[$firstTag]['line']) { |
218
|
|
|
break; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$indent = str_repeat(' ', $tokens[$stackPtr]['column']); |
225
|
|
|
$phpcsFile->fixer->addContent($prev, $phpcsFile->eolChar.$indent.'*'.$phpcsFile->eolChar); |
226
|
|
|
$phpcsFile->fixer->endChangeset(); |
227
|
|
|
} |
228
|
|
|
} |
229
|
4 |
|
} |
230
|
|
|
} |
231
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.