Skip to content

Commit 41c8f1c

Browse files
✨ Add better support for generic
1 parent b178c7d commit 41c8f1c

File tree

4 files changed

+121
-21
lines changed

4 files changed

+121
-21
lines changed

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,60 @@ class ValidTypeHintSniff implements Sniff
1717
/**
1818
* <simple> is any non-array, non-generic, non-alternated type, eg `int` or `\Foo`
1919
* <array> is array of <simple>, eg `int[]` or `\Foo[]`
20-
* <generic> is generic collection type, like `array<string, int>`, `Collection<Item>` and more complex like `Collection<int, \null|SubCollection<string>>`
21-
* <type> is <simple>, <array> or <generic> type, like `int`, `bool[]` or `Collection<ItemKey, ItemVal>`
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+
* <class-string> is Foo::class type, like `class-string` or `class-string<Foo>`
23+
* <type> is <simple>, <class-string>, <array>, <object> or <generic> type
2224
* <types> is one or more types alternated via `|`, like `int|bool[]|Collection<ItemKey, ItemVal>`
2325
*/
2426
private const REGEX_TYPES = '
2527
(?<types>
2628
(?<type>
2729
(?<generic>
28-
(?<genericName>(?&simple))\s*
29-
<\s*
30-
(?:
31-
(?<genericKey>(?&types))\s*
32-
,\s*
33-
)?
34-
(?<genericValue>(?&types)|(?&generic))\s*
35-
>
30+
(?<genericName>
31+
(?&simple)
32+
)
33+
\s*<\s*
34+
(?<genericContent>
35+
(?:(?&types)\s*,\s*)*
36+
(?&types)
37+
)
38+
\s*>
3639
)
3740
|
38-
(?<array>(?&simple)(\s*\[\s*\])+)
41+
(?<object>
42+
array\s*{\s*
43+
(?<objectContent>
44+
(?:
45+
(?<objectKeyValue>
46+
(?:\w+\s*\??:\s*)?
47+
(?&types)
48+
)
49+
\s*,\s*
50+
)*
51+
(?&objectKeyValue)
52+
)
53+
\s*}
54+
)
55+
|
56+
(?<array>
57+
(?&simple)(?:
58+
\s*\[\s*\]
59+
)+
60+
)
61+
|
62+
(?<classString>
63+
class-string(?:
64+
\s*<\s*[\\\\\w]+\s*>
65+
)?
66+
)
3967
|
40-
(?<simple>[@$?]?[\\\\\w]+)
68+
(?<simple>
69+
[@$?]?[\\\\\w]+
70+
)
4171
)
4272
(?:
43-
\s*\|\s*
44-
(?:(?&generic)|(?&array)|(?&simple))
73+
\s*\|\s*(?&type)
4574
)*
4675
)
4776
';
@@ -98,17 +127,14 @@ private function getValidTypes(string $content): string
98127
{
99128
$content = preg_replace('/\s/', '', $content);
100129
$types = $this->getTypes($content);
130+
101131
foreach ($types as $index => $type) {
102132
preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches);
103133

104134
if (isset($matches['generic']) && '' !== $matches['generic']) {
105-
$validType = $this->getValidType($matches['genericName']).'<';
106-
107-
if ('' !== $matches['genericKey']) {
108-
$validType .= $this->getValidTypes($matches['genericKey']).', ';
109-
}
110-
111-
$validType .= $this->getValidTypes($matches['genericValue']).'>';
135+
$validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']);
136+
} elseif (isset($matches['object']) && '' !== $matches['object']) {
137+
$validType = $this->getValidObjectType($matches['objectName'], $matches['objectContent']);
112138
} else {
113139
$validType = $this->getValidType($type);
114140
}
@@ -150,6 +176,53 @@ private function getTypes(string $content): array
150176
return $types;
151177
}
152178

179+
/**
180+
* @param string $genericName
181+
* @param string $genericContent
182+
*
183+
* @return string
184+
*/
185+
private function getValidGenericType(string $genericName, string $genericContent): string
186+
{
187+
$validType = $this->getValidType($genericName).'<';
188+
189+
while ('' !== $genericContent && false !== $genericContent) {
190+
preg_match('{^'.self::REGEX_TYPES.',?}x', $genericContent, $matches);
191+
192+
$validType .= $this->getValidTypes($matches['types']).', ';
193+
$genericContent = substr($genericContent, strlen($matches['types']) + 1);
194+
}
195+
196+
return preg_replace('/,\s$/', '>', $validType);
197+
}
198+
199+
/**
200+
* @param string $objectName
201+
* @param string $objectContent
202+
*
203+
* @return string
204+
*/
205+
private function getValidObjectType(string $objectName, string $objectContent): string
206+
{
207+
$validType = $this->getValidType($objectName).'{';
208+
209+
while ('' !== $objectContent && false !== $objectContent) {
210+
$split = preg_split('/(\??:|,)/', $objectContent, 2, PREG_SPLIT_DELIM_CAPTURE);
211+
212+
if (isset($split[1]) && ',' !== $split[1]) {
213+
$validType .= $split[0].$split[1].' ';
214+
$objectContent = $split[2];
215+
}
216+
217+
preg_match('{^'.self::REGEX_TYPES.',?}x', $objectContent, $matches);
218+
219+
$validType .= $this->getValidTypes($matches['types']).', ';
220+
$objectContent = substr($objectContent, strlen($matches['types']) + 1);
221+
}
222+
223+
return preg_replace('/,\s$/', '}', $validType);
224+
}
225+
153226
/**
154227
* @param string $typeName
155228
*

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,13 @@ echo ( float ) $a;
5353
*/
5454

5555
/** @method array < integer , null | boolean > | integer [ ] | array < array < integer > > truc */
56+
/** @method Generator<integer, string, string, boolean> truc */
57+
/** @method Generator<array<integer>, array<integer>, array<integer>, array<integer>> truc */
58+
/** @method array { integer: integer, boolean?: boolean } truc */
59+
/** @param class-string|class-string<Client>|integer truc */
60+
61+
/**
62+
* @param array{0: integer, 1?: integer}
63+
* @param array{integer, integer}
64+
* @param array{foo: integer, bar: string}
65+
*/

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,13 @@ echo ( float ) $a;
5353
*/
5454

5555
/** @method array<int, bool|null>|int[]|array<array<int>> truc */
56+
/** @method Generator<int, string, string, bool> truc */
57+
/** @method Generator<array<int>, array<int>, array<int>, array<int>> truc */
58+
/** @method array{integer: int, boolean?: bool} truc */
59+
/** @param class-string|class-string<Client>|int truc */
60+
61+
/**
62+
* @param array{0: int, 1?: int}
63+
* @param array{int, int}
64+
* @param array{foo: int, bar: string}
65+
*/

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ protected function getErrorList(): array
4141
51 => 1,
4242
52 => 1,
4343
55 => 1,
44+
56 => 1,
45+
57 => 1,
46+
58 => 1,
47+
59 => 1,
48+
62 => 1,
49+
63 => 1,
50+
64 => 1,
4451
];
4552
}
4653

0 commit comments

Comments
 (0)