1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace BestIt\CodeSniffer\Helper; |
6
|
|
|
|
7
|
|
|
use BestIt\CodeSniffer\File; |
8
|
|
|
use BestIt\Sniffs\Commenting\AbstractDocSniff; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Class DocDescriptionHelper |
12
|
|
|
* |
13
|
|
|
* @author Nick Lubisch <[email protected]> |
14
|
|
|
* @package BestIt\CodeSniffer\Helper |
15
|
|
|
*/ |
16
|
|
|
class DocDescriptionHelper |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* The php code sniffer file. |
20
|
|
|
* |
21
|
|
|
* @var File |
22
|
|
|
*/ |
23
|
|
|
private $file; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* The doc comment helper. |
27
|
|
|
* |
28
|
|
|
* @var DocHelper |
29
|
|
|
*/ |
30
|
|
|
private $docHelper; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* The token stack of the cs file. |
34
|
|
|
* |
35
|
|
|
* @var array |
36
|
|
|
*/ |
37
|
|
|
private $tokens; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* The doc comment summary helper. |
41
|
|
|
* |
42
|
|
|
* @var DocSummaryHelper |
43
|
|
|
*/ |
44
|
|
|
private $summaryHelper; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Indicator if a description is required. |
48
|
|
|
* |
49
|
|
|
* @var bool |
50
|
|
|
*/ |
51
|
|
|
private $descriptionRequired; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* DocSummaryHelper constructor. |
55
|
|
|
* |
56
|
|
|
* @param File $file The php cs file |
57
|
|
|
* @param DocHelper $docHelper The doc comment helper |
58
|
|
|
* @param DocSummaryHelper $summaryHelper The doc comment summary helper |
59
|
|
|
*/ |
60
|
121 |
|
public function __construct(File $file, DocHelper $docHelper, DocSummaryHelper $summaryHelper) |
61
|
|
|
{ |
62
|
121 |
|
$this->file = $file; |
63
|
121 |
|
$this->tokens = $file->getTokens(); |
64
|
121 |
|
$this->docHelper = $docHelper; |
65
|
121 |
|
$this->summaryHelper = $summaryHelper; |
66
|
121 |
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Checks for comment description. |
70
|
|
|
* |
71
|
|
|
* @param bool $descriptionRequired Indicator if the description is required. |
72
|
|
|
* |
73
|
|
|
* @return void |
74
|
|
|
*/ |
75
|
113 |
|
public function checkCommentDescription(bool $descriptionRequired) |
76
|
|
|
{ |
77
|
113 |
|
$this->descriptionRequired = $descriptionRequired; |
78
|
|
|
|
79
|
113 |
|
$commentStartToken = $this->docHelper->getBlockStartToken(); |
80
|
113 |
|
$commentEndToken = $this->docHelper->getBlockEndToken(); |
81
|
|
|
|
82
|
113 |
|
$summaryPtr = $this->summaryHelper->getCommentSummaryPosition(); |
83
|
|
|
|
84
|
113 |
|
if ($summaryPtr === -1) { |
85
|
4 |
|
return; |
86
|
|
|
} |
87
|
|
|
|
88
|
109 |
|
$hasTags = count($commentStartToken['comment_tags']) > 0; |
89
|
|
|
|
90
|
109 |
|
$descriptionStartPtr = $this->getCommentDescriptionStartPointer(); |
91
|
|
|
|
92
|
109 |
|
if ($descriptionStartPtr === -1) { |
93
|
24 |
|
$this->addDescriptionNotFoundError($summaryPtr); |
94
|
|
|
|
95
|
24 |
|
return; |
96
|
|
|
} |
97
|
|
|
|
98
|
86 |
|
$descriptionEndPtr = $this->getCommentDescriptionEndPointer(); |
99
|
86 |
|
$descEndToken = $this->tokens[$descriptionEndPtr]; |
100
|
|
|
|
101
|
86 |
|
$this->checkCommentDescriptionUcFirst($descriptionStartPtr); |
102
|
86 |
|
|
103
|
|
|
//Fix no or too much lines after description. |
104
|
|
|
$toLine = $commentEndToken['line']; |
105
|
86 |
|
$expectedLines = 0; |
106
|
86 |
|
|
107
|
|
|
if ($hasTags) { |
108
|
86 |
|
$firstTagPtr = array_shift($commentStartToken['comment_tags']); |
109
|
77 |
|
$firstTagToken = $this->tokens[$firstTagPtr]; |
110
|
77 |
|
$toLine = $firstTagToken['line']; |
111
|
77 |
|
|
112
|
|
|
$expectedLines = 1; |
113
|
77 |
|
} |
114
|
|
|
|
115
|
|
|
$diffLines = $toLine - $descEndToken['line'] - 1; |
116
|
86 |
|
|
117
|
|
|
if ($diffLines === $expectedLines) { |
118
|
86 |
|
return; |
119
|
75 |
|
} |
120
|
|
|
|
121
|
|
|
if ($diffLines < $expectedLines && $hasTags) { |
122
|
24 |
|
$fixNoLine = $this->file->addFixableError( |
123
|
12 |
|
AbstractDocSniff::MESSAGE_NO_LINE_AFTER_DESCRIPTION, |
124
|
12 |
|
$descriptionEndPtr, |
125
|
12 |
|
AbstractDocSniff::CODE_NO_LINE_AFTER_DESCRIPTION |
126
|
12 |
|
); |
127
|
|
|
|
128
|
|
|
if ($fixNoLine) { |
129
|
12 |
|
$this->fixNoLineAfterDescription(); |
130
|
8 |
|
} |
131
|
|
|
|
132
|
|
|
return; |
133
|
12 |
|
} |
134
|
|
|
|
135
|
|
|
$fixMuchLines = $this->file->addFixableError( |
136
|
16 |
|
AbstractDocSniff::MESSAGE_MUCH_LINES_AFTER_DESCRIPTION, |
137
|
16 |
|
$descriptionEndPtr, |
138
|
16 |
|
AbstractDocSniff::CODE_MUCH_LINES_AFTER_DESCRIPTION |
139
|
16 |
|
); |
140
|
|
|
|
141
|
|
|
if ($fixMuchLines) { |
142
|
16 |
|
$this->fixMuchLinesAfterDescription($descEndToken['line'] + 1, $toLine - 1); |
143
|
8 |
|
} |
144
|
|
|
} |
145
|
16 |
|
|
146
|
|
|
/** |
147
|
|
|
* Checks if the description starts with a capital letter. |
148
|
|
|
* |
149
|
|
|
* @param int $descriptionStartPtr Pointer to the start of the description. |
150
|
|
|
* |
151
|
|
|
* @return void |
152
|
|
|
*/ |
153
|
|
|
private function checkCommentDescriptionUcFirst(int $descriptionStartPtr) |
154
|
86 |
|
{ |
155
|
|
|
$descStartToken = $this->tokens[$descriptionStartPtr]; |
156
|
86 |
|
|
157
|
|
|
$descriptionContent = $descStartToken['content']; |
158
|
86 |
|
|
159
|
|
|
if (ucfirst($descriptionContent) === $descriptionContent) { |
160
|
86 |
|
return; |
161
|
82 |
|
} |
162
|
|
|
|
163
|
|
|
$fixUcFirst = $this->file->addFixableError( |
164
|
8 |
|
AbstractDocSniff::MESSAGE_DESCRIPTION_UC_FIRST, |
165
|
8 |
|
$descriptionStartPtr, |
166
|
8 |
|
AbstractDocSniff::CODE_DESCRIPTION_UC_FIRST |
167
|
8 |
|
); |
168
|
|
|
|
169
|
|
|
if ($fixUcFirst) { |
170
|
8 |
|
$this->fixDescriptionUcFirst($descriptionStartPtr); |
171
|
4 |
|
} |
172
|
|
|
} |
173
|
8 |
|
|
174
|
|
|
/** |
175
|
|
|
* Returns pointer to the end of the description. |
176
|
|
|
* |
177
|
|
|
* @return int Pointer to the end of the description or false |
178
|
|
|
*/ |
179
|
|
View Code Duplication |
private function getCommentDescriptionEndPointer(): int |
|
|
|
|
180
|
|
|
{ |
181
|
|
|
$descriptionStartPtr = $this->getCommentDescriptionStartPointer(); |
182
|
|
|
|
183
|
86 |
|
$commentStartToken = $this->docHelper->getBlockStartToken(); |
184
|
|
|
$commentEndPtr = $this->docHelper->getBlockEndPosition(); |
185
|
86 |
|
|
186
|
86 |
|
//If no tags found, possible end of search is the starting tag of the doc comment. |
187
|
86 |
|
if (count($commentStartToken['comment_tags']) === 0) { |
188
|
86 |
|
return $this->file->findPrevious( |
189
|
86 |
|
[T_DOC_COMMENT_STRING], |
190
|
|
|
$commentEndPtr - 1, |
191
|
|
|
$descriptionStartPtr |
192
|
86 |
|
); |
193
|
86 |
|
} |
194
|
86 |
|
|
195
|
|
|
//else its the pointer of the first comment tag found. |
196
|
|
|
$firstTagPtr = array_shift($commentStartToken['comment_tags']); |
197
|
86 |
|
|
198
|
86 |
|
return $this->file->findPrevious( |
199
|
|
|
[T_DOC_COMMENT_STRING], |
200
|
|
|
$firstTagPtr - 1, |
201
|
86 |
|
$descriptionStartPtr |
202
|
4 |
|
); |
203
|
4 |
|
} |
204
|
4 |
|
|
205
|
86 |
|
/** |
206
|
|
|
* Returns pointer to the start of the long description or false if not found. |
207
|
|
|
* |
208
|
|
|
* @return int Pointer to the start of the description or -1 |
209
|
86 |
|
*/ |
210
|
|
View Code Duplication |
private function getCommentDescriptionStartPointer(): int |
|
|
|
|
211
|
|
|
{ |
212
|
|
|
$commentStartToken = $this->docHelper->getBlockStartToken(); |
213
|
|
|
$commentEndPtr = $this->docHelper->getBlockEndPosition(); |
214
|
|
|
|
215
|
|
|
$summaryPtr = $this->summaryHelper->getCommentSummaryPosition(); |
216
|
86 |
|
|
217
|
|
|
//If no tags the possible end of search is the closing tag of the doc comment. |
218
|
86 |
|
if (count($commentStartToken['comment_tags']) === 0) { |
219
|
|
|
return $this->file->findNext( |
220
|
86 |
|
[T_DOC_COMMENT_STRING], |
221
|
86 |
|
$summaryPtr + 1, |
222
|
|
|
$commentEndPtr |
223
|
|
|
); |
224
|
86 |
|
} |
225
|
11 |
|
|
226
|
11 |
|
//else its the pointer of the first comment tag found. |
227
|
11 |
|
$firstTagPtr = array_shift($commentStartToken['comment_tags']); |
228
|
11 |
|
|
229
|
|
|
return $this->file->findNext( |
230
|
|
|
[T_DOC_COMMENT_STRING], |
231
|
|
|
$summaryPtr + 1, |
232
|
|
|
$firstTagPtr - 1 |
233
|
77 |
|
); |
234
|
|
|
} |
235
|
77 |
|
|
236
|
77 |
|
/** |
237
|
77 |
|
* Adds error when description is not found. |
238
|
77 |
|
* |
239
|
|
|
* @param int $summaryPtr Pointer to summary token. |
240
|
|
|
* |
241
|
|
|
* @return void |
242
|
|
|
*/ |
243
|
|
|
private function addDescriptionNotFoundError(int $summaryPtr) |
244
|
|
|
{ |
245
|
|
|
if ($this->descriptionRequired) { |
246
|
|
|
$this->file->addError( |
247
|
109 |
|
AbstractDocSniff::MESSAGE_DESCRIPTION_NOT_FOUND, |
248
|
|
|
$summaryPtr, |
249
|
109 |
|
AbstractDocSniff::CODE_DESCRIPTION_NOT_FOUND |
250
|
109 |
|
); |
251
|
|
|
} |
252
|
109 |
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
109 |
|
* Fixes no line after description. |
256
|
12 |
|
* |
257
|
12 |
|
* @return void |
258
|
12 |
|
*/ |
259
|
12 |
|
private function fixNoLineAfterDescription() |
260
|
|
|
{ |
261
|
|
|
$descEndPtr = $this->getCommentDescriptionEndPointer(); |
262
|
|
|
$descEndToken = $this->tokens[$descEndPtr]; |
263
|
|
|
|
264
|
99 |
|
$this->file->getFixer()->beginChangeset(); |
265
|
|
|
|
266
|
99 |
|
$this->file->getFixer()->addContent( |
267
|
99 |
|
$descEndPtr, |
268
|
99 |
|
$this->file->getEolChar() . str_repeat(' ', $descEndToken['level']) . ' *' |
269
|
99 |
|
); |
270
|
|
|
|
271
|
|
|
$this->file->getFixer()->endChangeset(); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Fixes much lines after description. |
276
|
|
|
* |
277
|
|
|
* @param int $startLine Line to start removing |
278
|
|
|
* @param int $endLine Line to end removing |
279
|
|
|
* |
280
|
24 |
|
* @return void |
281
|
|
|
*/ |
282
|
24 |
|
private function fixMuchLinesAfterDescription(int $startLine, int $endLine) |
283
|
4 |
|
{ |
284
|
4 |
|
$this->file->getFixer()->beginChangeset(); |
285
|
4 |
|
|
286
|
4 |
|
(new LineHelper($this->file))->removeLines($startLine, $endLine); |
287
|
|
|
|
288
|
|
|
$this->file->getFixer()->endChangeset(); |
289
|
24 |
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Fixes the description uc first. |
293
|
|
|
* |
294
|
|
|
* @param int $descriptionStartPtr Pointer to the description start |
295
|
|
|
* |
296
|
8 |
|
* @return void |
297
|
|
|
*/ |
298
|
8 |
|
private function fixDescriptionUcFirst(int $descriptionStartPtr) |
299
|
8 |
|
{ |
300
|
|
|
$descStartToken = $this->tokens[$descriptionStartPtr]; |
301
|
8 |
|
|
302
|
|
|
$this->file->getFixer()->beginChangeset(); |
303
|
8 |
|
|
304
|
8 |
|
$this->file->getFixer()->replaceToken( |
305
|
8 |
|
$descriptionStartPtr, |
306
|
|
|
ucfirst($descStartToken['content']) |
307
|
|
|
); |
308
|
8 |
|
|
309
|
8 |
|
$this->file->getFixer()->endChangeset(); |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
|
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.