Skip to content

Commit afb156f

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

File tree

4 files changed

+123
-21
lines changed

4 files changed

+123
-21
lines changed

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

Lines changed: 96 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,62 @@ 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+
(?<objectName>
43+
array
44+
)
45+
\s*{\s*
46+
(?<objectContent>
47+
(?:
48+
(?<objectKeyValue>
49+
(?:\w+\s*\??:\s*)?(?&types)
50+
)
51+
\s*,\s*
52+
)*
53+
(?&objectKeyValue)
54+
)
55+
\s*}
56+
)
57+
|
58+
(?<array>
59+
(?&simple)(?:
60+
\s*\[\s*\]
61+
)+
62+
)
63+
|
64+
(?<classString>
65+
class-string(?:
66+
\s*<\s*[\\\\\w]+\s*>
67+
)?
68+
)
3969
|
40-
(?<simple>[@$?]?[\\\\\w]+)
70+
(?<simple>
71+
[@$?]?[\\\\\w]+
72+
)
4173
)
4274
(?:
43-
\s*\|\s*
44-
(?:(?&generic)|(?&array)|(?&simple))
75+
\s*\|\s*(?&type)
4576
)*
4677
)
4778
';
@@ -98,17 +129,14 @@ private function getValidTypes(string $content): string
98129
{
99130
$content = preg_replace('/\s/', '', $content);
100131
$types = $this->getTypes($content);
132+
101133
foreach ($types as $index => $type) {
102134
preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches);
103135

104136
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']).'>';
137+
$validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']);
138+
} elseif (isset($matches['object']) && '' !== $matches['object']) {
139+
$validType = $this->getValidObjectType($matches['objectName'], $matches['objectContent']);
112140
} else {
113141
$validType = $this->getValidType($type);
114142
}
@@ -150,6 +178,53 @@ private function getTypes(string $content): array
150178
return $types;
151179
}
152180

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

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)