Skip to content

Commit dc1665b

Browse files
committed
Fix docblock sniff.
1 parent 5c2816b commit dc1665b

File tree

1 file changed

+187
-87
lines changed

1 file changed

+187
-87
lines changed

PhpCollective/Sniffs/Commenting/DocBlockVarSniff.php

Lines changed: 187 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@ public function process(File $phpCsFile, $stackPointer): void
4646
$type[] = $tokens[$previousIndex]['content'];
4747
$previousIndex = $phpCsFile->findPrevious(Tokens::$emptyTokens, $previousIndex - 1, null, true);
4848
}
49+
$type = array_reverse($type);
4950

5051
// Skip these checks for typed ones for now
5152
if ($type) {
53+
$this->checkTyped($phpCsFile, $stackPointer, $type);
54+
5255
return;
5356
}
5457

@@ -69,93 +72,7 @@ public function process(File $phpCsFile, $stackPointer): void
6972
return;
7073
}
7174

72-
/** @var int $docBlockStartIndex */
73-
$docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];
74-
75-
$defaultValueType = $this->findDefaultValueType($phpCsFile, $stackPointer);
76-
77-
$varIndex = null;
78-
for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
79-
if ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {
80-
continue;
81-
}
82-
if (!in_array($tokens[$i]['content'], ['@var'], true)) {
83-
continue;
84-
}
85-
86-
$varIndex = $i;
87-
}
88-
89-
if (!$varIndex) {
90-
$this->handleMissingVar($phpCsFile, $docBlockEndIndex, $docBlockStartIndex, $defaultValueType);
91-
92-
return;
93-
}
94-
95-
$classNameIndex = $varIndex + 2;
96-
97-
if ($tokens[$classNameIndex]['type'] !== 'T_DOC_COMMENT_STRING') {
98-
$this->handleMissingVarType($phpCsFile, $varIndex, $defaultValueType);
99-
100-
return;
101-
}
102-
103-
$content = $tokens[$classNameIndex]['content'];
104-
105-
$appendix = '';
106-
$spaceIndex = strpos($content, ' ');
107-
if ($spaceIndex) {
108-
$appendix = substr($content, $spaceIndex);
109-
$content = substr($content, 0, $spaceIndex);
110-
}
111-
112-
if (!$content) {
113-
$error = 'Doc Block type for property annotation @var missing';
114-
if ($defaultValueType) {
115-
$error .= ', type `' . $defaultValueType . '` detected';
116-
}
117-
$phpCsFile->addError($error, $stackPointer, 'VarTypeEmpty');
118-
119-
return;
120-
}
121-
122-
$comment = trim($appendix);
123-
if (mb_substr($comment, 0, 1) === '$') {
124-
$phpCsFile->addError('$var declaration only valid/needed inside inline doc blocks.', $stackPointer, 'CommentInvalid');
125-
}
126-
127-
if ($defaultValueType === null) {
128-
return;
129-
}
130-
131-
$parts = explode('|', $content);
132-
if (in_array($defaultValueType, $parts, true)) {
133-
return;
134-
}
135-
if ($defaultValueType === 'array' && ($this->containsTypeArray($parts) || $this->containsTypeArray($parts, 'list'))) {
136-
return;
137-
}
138-
if ($defaultValueType === 'false' && in_array('bool', $parts, true)) {
139-
return;
140-
}
141-
142-
if ($defaultValueType === 'false') {
143-
$defaultValueType = 'bool';
144-
}
145-
146-
if (count($parts) > 1 || $defaultValueType === 'null') {
147-
$fix = $phpCsFile->addFixableError('Doc Block type for property annotation @var incorrect, type `' . $defaultValueType . '` missing', $stackPointer, 'VarTypeMissing');
148-
if ($fix) {
149-
$phpCsFile->fixer->replaceToken($classNameIndex, implode('|', $parts) . '|' . $defaultValueType . $appendix);
150-
}
151-
152-
return;
153-
}
154-
155-
$fix = $phpCsFile->addFixableError('Doc Block type `' . $content . '` for property annotation @var incorrect, type `' . $defaultValueType . '` expected', $stackPointer, 'VarTypeIncorrect');
156-
if ($fix) {
157-
$phpCsFile->fixer->replaceToken($classNameIndex, $defaultValueType . $appendix);
158-
}
75+
$this->handle($phpCsFile, $docBlockEndIndex, $stackPointer);
15976
}
16077

16178
/**
@@ -287,4 +204,187 @@ protected function handleMissingVarType(File $phpCsFile, int $varIndex, ?string
287204

288205
$phpCsFile->fixer->addContent($varIndex, ' ' . $defaultValueType);
289206
}
207+
208+
/**
209+
* @param \PHP_CodeSniffer\Files\File $phpCsFile
210+
* @param int $stackPointer
211+
* @param array<string> $types
212+
*
213+
* @return void
214+
*/
215+
protected function checkTyped(File $phpCsFile, int $stackPointer, array $types): void
216+
{
217+
foreach ($types as $key => $value) {
218+
if ($value === '?') {
219+
unset($types[$key]);
220+
$types[] = 'null';
221+
}
222+
if ($value === '|') {
223+
unset($types[$key]);
224+
}
225+
}
226+
227+
$docBlockEndIndex = $this->findRelatedDocBlock($phpCsFile, $stackPointer);
228+
229+
if (!$docBlockEndIndex) {
230+
return;
231+
}
232+
233+
$this->handle($phpCsFile, $docBlockEndIndex, $stackPointer, $types);
234+
}
235+
236+
/**
237+
* @param \PHP_CodeSniffer\Files\File $phpCsFile
238+
* @param int $docBlockEndIndex
239+
* @param int $stackPointer
240+
* @param array<string> $types
241+
*
242+
* @return void
243+
*/
244+
protected function handle(File $phpCsFile, int $docBlockEndIndex, int $stackPointer, array $types = []): void
245+
{
246+
$tokens = $phpCsFile->getTokens();
247+
248+
/** @var int $docBlockStartIndex */
249+
$docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];
250+
251+
$defaultValueType = $this->findDefaultValueType($phpCsFile, $stackPointer);
252+
253+
$varIndex = null;
254+
for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
255+
if ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {
256+
continue;
257+
}
258+
if (!in_array($tokens[$i]['content'], ['@var'], true)) {
259+
continue;
260+
}
261+
262+
$varIndex = $i;
263+
}
264+
265+
if (!$varIndex) {
266+
if ($types) {
267+
return;
268+
}
269+
270+
$this->handleMissingVar($phpCsFile, $docBlockEndIndex, $docBlockStartIndex, $defaultValueType);
271+
272+
return;
273+
}
274+
275+
$classNameIndex = $varIndex + 2;
276+
277+
if ($tokens[$classNameIndex]['type'] !== 'T_DOC_COMMENT_STRING') {
278+
$this->handleMissingVarType($phpCsFile, $varIndex, $defaultValueType);
279+
280+
return;
281+
}
282+
283+
$content = $tokens[$classNameIndex]['content'];
284+
if (str_contains($content, '{') || str_contains($content, '<')) {
285+
return;
286+
}
287+
288+
$appendix = '';
289+
$spaceIndex = strpos($content, ' ');
290+
if ($spaceIndex) {
291+
$appendix = substr($content, $spaceIndex);
292+
$content = substr($content, 0, $spaceIndex);
293+
}
294+
295+
if (!$content) {
296+
$error = 'Doc Block type for property annotation @var missing';
297+
if ($defaultValueType) {
298+
$error .= ', type `' . $defaultValueType . '` detected';
299+
}
300+
$phpCsFile->addError($error, $stackPointer, 'VarTypeEmpty');
301+
302+
return;
303+
}
304+
305+
$comment = trim($appendix);
306+
if (mb_substr($comment, 0, 1) === '$') {
307+
$phpCsFile->addError('$var declaration only valid/needed inside inline doc blocks.', $stackPointer, 'CommentInvalid');
308+
}
309+
310+
$this->handleDefaultValue($phpCsFile, $stackPointer, $defaultValueType, $content, $appendix, $classNameIndex);
311+
$this->handleTypes($phpCsFile, $stackPointer, $types, $content, $appendix, $classNameIndex);
312+
}
313+
314+
/**
315+
* @param \PHP_CodeSniffer\Files\File $phpCsFile
316+
* @param int $stackPointer
317+
* @param string|null $defaultValueType
318+
* @param string $content
319+
* @param string $appendix
320+
* @param int $classNameIndex
321+
*
322+
* @return void
323+
*/
324+
protected function handleDefaultValue(
325+
File $phpCsFile,
326+
int $stackPointer,
327+
?string $defaultValueType,
328+
string $content,
329+
string $appendix,
330+
int $classNameIndex,
331+
): void {
332+
if ($defaultValueType === null) {
333+
return;
334+
}
335+
336+
$parts = explode('|', $content);
337+
338+
if (in_array($defaultValueType, $parts, true)) {
339+
return;
340+
}
341+
if ($defaultValueType === 'array' && ($this->containsTypeArray($parts) || $this->containsTypeArray($parts, 'list'))) {
342+
return;
343+
}
344+
if ($defaultValueType === 'false' && in_array('bool', $parts, true)) {
345+
return;
346+
}
347+
348+
if ($defaultValueType === 'false') {
349+
$defaultValueType = 'bool';
350+
}
351+
352+
if (count($parts) > 1 || $defaultValueType === 'null') {
353+
$fix = $phpCsFile->addFixableError('Doc Block type for property annotation @var incorrect, type `' . $defaultValueType . '` missing', $stackPointer, 'VarTypeMissing');
354+
if ($fix) {
355+
$phpCsFile->fixer->replaceToken($classNameIndex, implode('|', $parts) . '|' . $defaultValueType . $appendix);
356+
}
357+
358+
return;
359+
}
360+
361+
$fix = $phpCsFile->addFixableError('Doc Block type `' . $content . '` for property annotation @var incorrect, type `' . $defaultValueType . '` expected', $stackPointer, 'VarTypeIncorrect');
362+
if ($fix) {
363+
$phpCsFile->fixer->replaceToken($classNameIndex, $defaultValueType . $appendix);
364+
}
365+
}
366+
367+
/**
368+
* @param \PHP_CodeSniffer\Files\File $phpCsFile
369+
* @param int $stackPointer
370+
* @param array<string> $types
371+
* @param mixed $content
372+
* @param string $appendix
373+
* @param int $classNameIndex
374+
*
375+
* @return void
376+
*/
377+
protected function handleTypes(File $phpCsFile, int $stackPointer, array $types, mixed $content, string $appendix, int $classNameIndex): void
378+
{
379+
foreach ($types as $type) {
380+
if (str_contains($content, $type)) {
381+
continue;
382+
}
383+
384+
$fix = $phpCsFile->addFixableError('Doc Block type `' . $content . '` for property annotation @var incorrect, type `' . $type . '` missing', $stackPointer, 'VarTypeMissing');
385+
if ($fix) {
386+
$phpCsFile->fixer->replaceToken($classNameIndex, $content . '|' . $type . $appendix);
387+
}
388+
}
389+
}
290390
}

0 commit comments

Comments
 (0)