Skip to content

Commit f69d489

Browse files
📦 Drop some type hint support to improve the way it works
1 parent b2b65ef commit f69d489

File tree

4 files changed

+85
-86
lines changed

4 files changed

+85
-86
lines changed

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

Lines changed: 83 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,36 @@ class ValidTypeHintSniff implements Sniff
2020
private const CLOSER = '\>|\]|\}|\)';
2121
private const SEPARATOR = '\&|\|';
2222

23+
# <simple> is any non-array, non-generic, non-alternated type, eg `int` or `\Foo`
24+
# <array> is array of <simple>, eg `int[]` or `\Foo[]`
25+
# <generic> is generic collection type, like `array<string, int>`, `Collection<Item>` and more complex like `Collection<int, \null|SubCollection<string>>`
26+
# <type> is <simple>, <array> or <generic> type, like `int`, `bool[]` or `Collection<ItemKey, ItemVal>`
27+
# <types> is one or more types alternated via `|`, like `int|bool[]|Collection<ItemKey, ItemVal>`
28+
private const REGEX_TYPES = '
29+
(?<types>
30+
(?<type>
31+
(?<array>
32+
(?&simple)(\[\])*
33+
)
34+
|
35+
(?<simple>
36+
[@$?]?[\\\\\w]+
37+
)
38+
|
39+
(?<generic>
40+
(?<genericName>(?&simple))
41+
<
42+
(?:(?<genericKey>(?&types)),\s*)?(?<genericValue>(?&types)|(?&generic))
43+
>
44+
)
45+
)
46+
(?:
47+
\|
48+
(?:(?&simple)|(?&array)|(?&generic))
49+
)*
50+
)
51+
';
52+
2353
/**
2454
* @return array
2555
*/
@@ -37,90 +67,93 @@ public function process(File $phpcsFile, $stackPtr): void
3767
$tokens = $phpcsFile->getTokens();
3868

3969
if (in_array($tokens[$stackPtr]['content'], SniffHelper::TAGS_WITH_TYPE)) {
40-
preg_match(
41-
'`^((?:'
42-
.'(?:'.self::OPENER.'|'.self::MIDDLE.'|'.self::SEPARATOR.')\s+'
43-
.'(?='.self::TEXT.'|'.self::OPENER.'|'.self::MIDDLE.'|'.self::CLOSER.'|'.self::SEPARATOR.')'
44-
.'|'.self::OPENER.'|'.self::MIDDLE.'|'.self::SEPARATOR
45-
.'|(?:'.self::TEXT.'|'.self::CLOSER.')\s+'
46-
.'(?='.self::OPENER.'|'.self::MIDDLE.'|'.self::CLOSER.'|'.self::SEPARATOR.')'
47-
.'|'.self::TEXT.'|'.self::CLOSER.''
48-
.')+)(.*)?`i',
49-
$tokens[($stackPtr + 2)]['content'],
50-
$match
70+
$matchingResult = preg_match(
71+
'{^'.self::REGEX_TYPES.'(?:[ \t].*)?$}sx',
72+
$tokens[$stackPtr + 2]['content'],
73+
$matches
5174
);
5275

53-
if (isset($match[1]) === false) {
54-
return;
55-
}
76+
$content = 1 === $matchingResult ? $matches['types'] : '';
77+
$endOfContent = preg_replace('/'.preg_quote($content, '/').'/', '', $tokens[$stackPtr + 2]['content'], 1);
78+
79+
$suggestedType = $this->getValidTypes($content);
5680

57-
$type = $match[1];
58-
$suggestedType = $this->getValidTypeName($type);
59-
if ($type !== $suggestedType) {
81+
if ($content !== $suggestedType) {
6082
$fix = $phpcsFile->addFixableError(
6183
'For type-hinting in PHPDocs, use %s instead of %s',
6284
$stackPtr + 2,
6385
'Invalid',
64-
[$suggestedType, $type]
86+
[$suggestedType, $content]
6587
);
6688

6789
if ($fix) {
68-
$replacement = $suggestedType;
69-
if (isset($match[2])) {
70-
$replacement .= $match[2];
71-
}
72-
73-
$phpcsFile->fixer->replaceToken($stackPtr + 2, $replacement);
90+
$phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent);
7491
}
7592
}
7693
}
7794
}
7895

7996
/**
80-
* @param string $typeName
97+
* @param string $content
98+
*
99+
* @return array
100+
*/
101+
private function getTypes(string $content): array
102+
{
103+
$types = [];
104+
while ('' !== $content && false !== $content) {
105+
preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches);
106+
107+
$types[] = $matches['type'];
108+
$content = substr($content, strlen($matches['type']) + 1);
109+
}
110+
111+
return $types;
112+
}
113+
114+
/**
115+
* @param string $content
81116
*
82117
* @return string
83118
*/
84-
private function getValidTypeName(string $typeName): string
119+
private function getValidTypes(string $content): string
85120
{
86-
$typeNameWithoutSpace = str_replace(' ', '', $typeName);
87-
$parts = preg_split(
88-
'/('.self::OPENER.'|'.self::MIDDLE.'|'.self::CLOSER.'|'.self::SEPARATOR.')/',
89-
$typeNameWithoutSpace,
90-
-1,
91-
PREG_SPLIT_DELIM_CAPTURE
92-
);
93-
$partsNumber = count($parts) - 1;
94-
95-
$validType = '';
96-
for ($i = 0; $i < $partsNumber; $i += 2) {
97-
$validType .= $this->suggestType($parts[$i]);
98-
99-
if ('=>' === $parts[$i + 1]) {
100-
$validType .= ' ';
101-
}
121+
$types = $this->getTypes($content);
122+
123+
foreach ($types as $index => $type) {
124+
$type = str_replace(' ', '', $type);
102125

103-
$validType .= $parts[$i + 1];
126+
preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches);
127+
if (isset($matches['generic'])) {
128+
$validType = $this->getValidType($matches['genericName']).'<';
104129

105-
if (preg_match('/'.self::MIDDLE.'/', $parts[$i + 1])) {
106-
$validType .= ' ';
130+
if ('' !== $matches['genericKey']) {
131+
$validType .= $this->getValidTypes($matches['genericKey']).', ';
132+
}
133+
134+
$validType .= $this->getValidTypes($matches['genericValue']).'>';
135+
} else {
136+
$validType = $this->getValidType($type);
107137
}
108-
}
109138

110-
if ('' !== $parts[$partsNumber]) {
111-
$validType .= $this->suggestType($parts[$partsNumber]);
139+
$types[$index] = $validType;
112140
}
113141

114-
return trim($validType);
142+
return implode('|', $types);
143+
115144
}
116145

117146
/**
118147
* @param string $typeName
119148
*
120149
* @return string
121150
*/
122-
private function suggestType(string $typeName): string
151+
private function getValidType(string $typeName): string
123152
{
153+
if ('[]' === substr($typeName, -2)) {
154+
return $this->getValidType(substr($typeName, 0, -2)).'[]';
155+
}
156+
124157
$lowerType = strtolower($typeName);
125158
switch ($lowerType) {
126159
case 'bool':

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,4 @@ echo ( float ) $a;
4444

4545
/** @method integer|string */
4646
/** @method array<integer,boolean>|integer[]|array<integer> truc */
47-
/** @method array<integer, boolean>| integer[] |string|(integer|boolean)[] truc */
48-
/** @method array{scheme:string,host:string} truc */
49-
/** @method array(int=>string) truc */
50-
51-
/**
52-
* Avoid weird bug
53-
*
54-
* @method array{
55-
* @method array{scheme:integer,host:
56-
* @method array<
57-
* @method array<integer,
58-
* @method array|
59-
*/
60-
/** @method array<integer, */
61-
/** @method array<integer, $truc */
47+
/** @method array<integer, boolean|null>|integer[]|array<array<integer>> truc */

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,4 @@ echo ( float ) $a;
4444

4545
/** @method int|string */
4646
/** @method array<int, bool>|int[]|array<int> truc */
47-
/** @method array<int, bool>|int[]|string|(int|bool)[] truc */
48-
/** @method array{scheme: string, host: string} truc */
49-
/** @method array(int => string) truc */
50-
51-
/**
52-
* Avoid weird bug
53-
*
54-
* @method array{
55-
* @method array{scheme: int, host:
56-
* @method array<
57-
* @method array<int,
58-
* @method array|
59-
*/
60-
/** @method array<int, */
61-
/** @method array<int, $truc */
47+
/** @method array<int, bool|null>|int[]|array<array<int>> truc */

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@ protected function getErrorList(): array
3737
45 => 1,
3838
46 => 1,
3939
47 => 1,
40-
48 => 1,
41-
49 => 1,
42-
55 => 1,
43-
57 => 1,
44-
60 => 1,
45-
61 => 1,
4640
];
4741
}
4842

0 commit comments

Comments
 (0)