Skip to content

Commit 0ed867a

Browse files
Merge pull request #99 from VincentLanglet/wip
📦 Drop some type hint support to improve the way it works
2 parents b2b65ef + 3978c75 commit 0ed867a

File tree

4 files changed

+86
-86
lines changed

4 files changed

+86
-86
lines changed

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

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

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

3971
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
72+
$matchingResult = preg_match(
73+
'{^'.self::REGEX_TYPES.'(?:[ \t].*)?$}sx',
74+
$tokens[$stackPtr + 2]['content'],
75+
$matches
5176
);
5277

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

57-
$type = $match[1];
58-
$suggestedType = $this->getValidTypeName($type);
59-
if ($type !== $suggestedType) {
81+
$suggestedType = $this->getValidTypes($content);
82+
83+
if ($content !== $suggestedType) {
6084
$fix = $phpcsFile->addFixableError(
6185
'For type-hinting in PHPDocs, use %s instead of %s',
6286
$stackPtr + 2,
6387
'Invalid',
64-
[$suggestedType, $type]
88+
[$suggestedType, $content]
6589
);
6690

6791
if ($fix) {
68-
$replacement = $suggestedType;
69-
if (isset($match[2])) {
70-
$replacement .= $match[2];
71-
}
72-
73-
$phpcsFile->fixer->replaceToken($stackPtr + 2, $replacement);
92+
$phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent);
7493
}
7594
}
7695
}
7796
}
7897

7998
/**
80-
* @param string $typeName
99+
* @param string $content
100+
*
101+
* @return array
102+
*/
103+
private function getTypes(string $content): array
104+
{
105+
$types = [];
106+
while ('' !== $content && false !== $content) {
107+
preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches);
108+
109+
$types[] = $matches['type'];
110+
$content = substr($content, strlen($matches['type']) + 1);
111+
}
112+
113+
return $types;
114+
}
115+
116+
/**
117+
* @param string $content
81118
*
82119
* @return string
83120
*/
84-
private function getValidTypeName(string $typeName): string
121+
private function getValidTypes(string $content): string
85122
{
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-
}
123+
$types = $this->getTypes($content);
124+
125+
foreach ($types as $index => $type) {
126+
$type = str_replace(' ', '', $type);
102127

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

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

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

114-
return trim($validType);
144+
return implode('|', $types);
115145
}
116146

117147
/**
118148
* @param string $typeName
119149
*
120150
* @return string
121151
*/
122-
private function suggestType(string $typeName): string
152+
private function getValidType(string $typeName): string
123153
{
154+
if ('[]' === substr($typeName, -2)) {
155+
return $this->getValidType(substr($typeName, 0, -2)).'[]';
156+
}
157+
124158
$lowerType = strtolower($typeName);
125159
switch ($lowerType) {
126160
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)