Skip to content

Commit 82360b0

Browse files
✨ Function comment improvements
1 parent d71b89b commit 82360b0

File tree

8 files changed

+285
-89
lines changed

8 files changed

+285
-89
lines changed

SymfonyCustom/Helpers/SniffHelper.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,70 @@ class SniffHelper extends AbstractHelper
5656
'@var',
5757
];
5858

59+
/**
60+
* <simple> is any non-array, non-generic, non-alternated type, eg `int` or `\Foo`
61+
* <array> is array of <simple>, eg `int[]` or `\Foo[]`
62+
* <generic> is generic collection type, like `array<string, int>`, `Collection<Item>` or more complex`
63+
* <object> is array key => value type, like `array{type: string, name: string, value: mixed}`
64+
* <type> is <simple>, <array>, <object>, <generic> type
65+
* <types> is one or more types alternated via `|`, like `int|bool[]|Collection<ItemKey, ItemVal>`
66+
*/
67+
public const REGEX_TYPES = '
68+
(?<types>
69+
(?<type>
70+
(?<array>
71+
(?&notArray)(?:
72+
\s*\[\s*\]
73+
)+
74+
)
75+
|
76+
(?<notArray>
77+
(?<multiple>
78+
\(\s*(?<mutipleContent>
79+
(?&types)
80+
)\s*\)
81+
)
82+
|
83+
(?<generic>
84+
(?<genericName>
85+
(?&simple)
86+
)
87+
\s*<\s*
88+
(?<genericContent>
89+
(?:(?&types)\s*,\s*)*
90+
(?&types)
91+
)
92+
\s*>
93+
)
94+
|
95+
(?<object>
96+
array\s*{\s*
97+
(?<objectContent>
98+
(?:
99+
(?<objectKeyValue>
100+
(?:\w+\s*\??:\s*)?
101+
(?&types)
102+
)
103+
\s*,\s*
104+
)*
105+
(?&objectKeyValue)
106+
)
107+
\s*}
108+
)
109+
|
110+
(?<simple>
111+
\\\\?\w+(?:\\\\\w+)*
112+
|
113+
\$this
114+
)
115+
)
116+
)
117+
(?:
118+
\s*[\|&]\s*(?&type)
119+
)*
120+
)
121+
';
122+
59123
/**
60124
* @param File $phpcsFile
61125
* @param int $stackPtr
@@ -122,4 +186,20 @@ public static function isGlobalUse(File $phpcsFile, int $stackPtr): bool
122186

123187
return true;
124188
}
189+
190+
/**
191+
* @param string $content
192+
*
193+
* @return array
194+
*/
195+
public static function parseTypeHint(string $content): array
196+
{
197+
preg_match(
198+
'{^'.SniffHelper::REGEX_TYPES.'(?<space>[\s\t]*)?(?<description>.*)?$}six',
199+
$content,
200+
$matches
201+
);
202+
203+
return [$matches['types'] ?? '', $matches['space'] ?? '', $matches['description'] ?? ''];
204+
}
125205
}

SymfonyCustom/Sniffs/Commenting/FunctionCommentSniff.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHP_CodeSniffer\Files\File;
88
use PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff as PEARFunctionCommentSniff;
99
use PHP_CodeSniffer\Util\Tokens;
10+
use SymfonyCustom\Helpers\SniffHelper;
1011

1112
/**
1213
* SymfonyCustom standard customization to PEARs FunctionCommentSniff.
@@ -144,6 +145,21 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart, $has
144145
$error = 'Return type missing for @return tag in function comment';
145146
$phpcsFile->addError($error, $return, 'MissingReturnType');
146147
}
148+
149+
[$type, $space, $description] = SniffHelper::parseTypeHint($content);
150+
if (preg_match('/^\$\S+/', $description)) {
151+
$error = '@return annotations should not contain variable name';
152+
$fix = $phpcsFile->addFixableError($error, $return, 'NamedReturn');
153+
154+
if ($fix) {
155+
$description = preg_replace('/^\$\S+/', '', $description);
156+
if ('' !== $description) {
157+
$phpcsFile->fixer->replaceToken($return + 2, $type.$space.$description);
158+
} else {
159+
$phpcsFile->fixer->replaceToken($return + 2, $type);
160+
}
161+
}
162+
}
147163
} else {
148164
$error = 'Missing @return tag in function comment';
149165
$phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn');

SymfonyCustom/Sniffs/Commenting/VariableCommentSniff.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PHP_CodeSniffer\Files\File;
88
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
9+
use SymfonyCustom\Helpers\SniffHelper;
910

1011
/**
1112
* Parses and verifies the variable doc comment.
@@ -97,19 +98,21 @@ public function processMemberVar(File $phpcsFile, $stackPtr): void
9798
return;
9899
}
99100

100-
$content = explode(' ', $tokens[$string]['content']);
101-
$newContent = array_filter($content, function ($value) use ($tokens, $stackPtr) {
102-
return 0 === preg_match('/^\$/', $value);
103-
});
104-
if (count($newContent) < count($content)) {
101+
[$type, $space, $description] = SniffHelper::parseTypeHint($tokens[$string]['content']);
102+
if (preg_match('/^\$\S+/', $description)) {
105103
$fix = $phpcsFile->addFixableError(
106104
'@var annotations should not contain variable name',
107105
$foundVar,
108106
'NamedVar'
109107
);
110108

111109
if ($fix) {
112-
$phpcsFile->fixer->replaceToken($string, implode(' ', $newContent));
110+
$description = preg_replace('/^\$\S+/', '', $description);
111+
if ('' !== $description) {
112+
$phpcsFile->fixer->replaceToken($string, $type.$space.$description);
113+
} else {
114+
$phpcsFile->fixer->replaceToken($string, $type);
115+
}
113116
}
114117
}
115118
}

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

Lines changed: 8 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -14,70 +14,6 @@
1414
*/
1515
class ValidTypeHintSniff implements Sniff
1616
{
17-
/**
18-
* <simple> is any non-array, non-generic, non-alternated type, eg `int` or `\Foo`
19-
* <array> is array of <simple>, eg `int[]` or `\Foo[]`
20-
* <generic> is generic collection type, like `array<string, int>`, `Collection<Item>` or more complex`
21-
* <object> is array key => value type, like `array{type: string, name: string, value: mixed}`
22-
* <type> is <simple>, <array>, <object>, <generic> type
23-
* <types> is one or more types alternated via `|`, like `int|bool[]|Collection<ItemKey, ItemVal>`
24-
*/
25-
private const REGEX_TYPES = '
26-
(?<types>
27-
(?<type>
28-
(?<array>
29-
(?&notArray)(?:
30-
\s*\[\s*\]
31-
)+
32-
)
33-
|
34-
(?<notArray>
35-
(?<multiple>
36-
\(\s*(?<mutipleContent>
37-
(?&types)
38-
)\s*\)
39-
)
40-
|
41-
(?<generic>
42-
(?<genericName>
43-
(?&simple)
44-
)
45-
\s*<\s*
46-
(?<genericContent>
47-
(?:(?&types)\s*,\s*)*
48-
(?&types)
49-
)
50-
\s*>
51-
)
52-
|
53-
(?<object>
54-
array\s*{\s*
55-
(?<objectContent>
56-
(?:
57-
(?<objectKeyValue>
58-
(?:\w+\s*\??:\s*)?
59-
(?&types)
60-
)
61-
\s*,\s*
62-
)*
63-
(?&objectKeyValue)
64-
)
65-
\s*}
66-
)
67-
|
68-
(?<simple>
69-
\\\\?\w+(?:\\\\\w+)*
70-
|
71-
\$this
72-
)
73-
)
74-
)
75-
(?:
76-
\s*[\|&]\s*(?&type)
77-
)*
78-
)
79-
';
80-
8117
/**
8218
* False if the type is not a reserved keyword and the check can't be case insensitive
8319
**/
@@ -132,17 +68,10 @@ public function process(File $phpcsFile, $stackPtr): void
13268
return;
13369
}
13470

135-
$matchingResult = preg_match(
136-
'{^'.self::REGEX_TYPES.'(?:[\s\t].*)?$}six',
137-
$tokens[$stackPtr + 2]['content'],
138-
$matches
139-
);
140-
141-
$content = 1 === $matchingResult ? $matches['types'] : '';
142-
$endOfContent = substr($tokens[$stackPtr + 2]['content'], strlen($content));
71+
[$type, $space, $description] = SniffHelper::parseTypeHint($tokens[$stackPtr + 2]['content']);
14372

14473
try {
145-
$suggestedType = $this->getValidTypes($content);
74+
$suggestedType = $this->getValidTypes($type);
14675
} catch (DeepExitException $exception) {
14776
$phpcsFile->addError(
14877
$exception->getMessage(),
@@ -153,16 +82,16 @@ public function process(File $phpcsFile, $stackPtr): void
15382
return;
15483
}
15584

156-
if ($content !== $suggestedType) {
85+
if ($type !== $suggestedType) {
15786
$fix = $phpcsFile->addFixableError(
15887
'For type-hinting in PHPDocs, use %s instead of %s',
15988
$stackPtr + 2,
16089
'Invalid',
161-
[$suggestedType, $content]
90+
[$suggestedType, $type]
16291
);
16392

16493
if ($fix) {
165-
$phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent);
94+
$phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$space.$description);
16695
}
16796
}
16897
}
@@ -181,7 +110,7 @@ private function getValidTypes(string $content): string
181110
$types = [];
182111
$separators = [];
183112
while ('' !== $content && false !== $content) {
184-
preg_match('{^'.self::REGEX_TYPES.'$}ix', $content, $matches);
113+
preg_match('{^'.SniffHelper::REGEX_TYPES.'$}ix', $content, $matches);
185114

186115
if (isset($matches['array']) && '' !== $matches['array']) {
187116
$validType = $this->getValidTypes(substr($matches['array'], 0, -2)).'[]';
@@ -253,7 +182,7 @@ private function getValidGenericType(string $genericName, string $genericContent
253182
$validType = $this->getValidType($genericName).'<';
254183

255184
while ('' !== $genericContent && false !== $genericContent) {
256-
preg_match('{^'.self::REGEX_TYPES.',?}ix', $genericContent, $matches);
185+
preg_match('{^'.SniffHelper::REGEX_TYPES.',?}ix', $genericContent, $matches);
257186

258187
$validType .= $this->getValidTypes($matches['types']).', ';
259188
$genericContent = substr($genericContent, strlen($matches['types']) + 1);
@@ -281,7 +210,7 @@ private function getValidObjectType(string $objectContent): string
281210
$objectContent = $split[2];
282211
}
283212

284-
preg_match('{^'.self::REGEX_TYPES.',?}ix', $objectContent, $matches);
213+
preg_match('{^'.SniffHelper::REGEX_TYPES.',?}ix', $objectContent, $matches);
285214

286215
$validType .= $this->getValidTypes($matches['types']).', ';
287216
$objectContent = substr($objectContent, strlen($matches['types']) + 1);

SymfonyCustom/Tests/Commenting/FunctionCommentUnitTest.inc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,20 @@ function throwTest3()
127127
{
128128
throw new Exception();
129129
}
130+
131+
/**
132+
* @return int
133+
* @return int
134+
*/
135+
function multiReturn()
136+
{
137+
return 1;
138+
}
139+
140+
/**
141+
* @return int $foo
142+
*/
143+
function returnWithVariable()
144+
{
145+
return 1;
146+
}

0 commit comments

Comments
 (0)