Skip to content

Commit e56dc85

Browse files
✨ Handle more type definitions
1 parent 9939c03 commit e56dc85

File tree

4 files changed

+145
-134
lines changed

4 files changed

+145
-134
lines changed

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

Lines changed: 113 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace SymfonyCustom\Sniffs\NamingConventions;
66

7+
use PHP_CodeSniffer\Exceptions\DeepExitException;
78
use PHP_CodeSniffer\Files\File;
89
use PHP_CodeSniffer\Sniffs\Sniff;
910
use PHP_CodeSniffer\Util\Common;
@@ -26,51 +27,59 @@ class ValidTypeHintSniff implements Sniff
2627
private const REGEX_TYPES = '
2728
(?<types>
2829
(?<type>
29-
(?<generic>
30-
(?<genericName>
31-
(?&simple)
32-
)
33-
\s*<\s*
34-
(?<genericContent>
35-
(?:(?&types)\s*,\s*)*
36-
(?&types)
37-
)
38-
\s*>
39-
)
40-
|
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-
|
5630
(?<array>
57-
(?&simple)(?:
31+
(?&notArray)(?:
5832
\s*\[\s*\]
5933
)+
6034
)
6135
|
62-
(?<classString>
63-
class-string(?:
64-
\s*<\s*[\\\\\w]+\s*>
65-
)?
66-
)
67-
|
68-
(?<simple>
69-
[@$?]?[\\\\\w]+
36+
(?<notArray>
37+
(?<multiple>
38+
\(\s*(?<mutipleContent>
39+
(?&types)
40+
)\s*\)
41+
)
42+
|
43+
(?<generic>
44+
(?<genericName>
45+
(?&simple)
46+
)
47+
\s*<\s*
48+
(?<genericContent>
49+
(?:(?&types)\s*,\s*)*
50+
(?&types)
51+
)
52+
\s*>
53+
)
54+
|
55+
(?<object>
56+
array\s*{\s*
57+
(?<objectContent>
58+
(?:
59+
(?<objectKeyValue>
60+
(?:\w+\s*\??:\s*)?
61+
(?&types)
62+
)
63+
\s*,\s*
64+
)*
65+
(?&objectKeyValue)
66+
)
67+
\s*}
68+
)
69+
|
70+
(?<classString>
71+
class-string(?:
72+
\s*<\s*[\\\\\w]+\s*>
73+
)?
74+
)
75+
|
76+
(?<simple>
77+
[@$?]?[\\\\\w]+
78+
)
7079
)
7180
)
7281
(?:
73-
\s*\|\s*(?&type)
82+
\s*[\|&]\s*(?&type)
7483
)*
7584
)
7685
';
@@ -91,29 +100,41 @@ public function process(File $phpcsFile, $stackPtr): void
91100
{
92101
$tokens = $phpcsFile->getTokens();
93102

94-
if (in_array($tokens[$stackPtr]['content'], SniffHelper::TAGS_WITH_TYPE)) {
95-
$matchingResult = preg_match(
96-
'{^'.self::REGEX_TYPES.'(?:[ \t].*)?$}sx',
97-
$tokens[$stackPtr + 2]['content'],
98-
$matches
99-
);
103+
if (!in_array($tokens[$stackPtr]['content'], SniffHelper::TAGS_WITH_TYPE)) {
104+
return;
105+
}
106+
107+
$matchingResult = preg_match(
108+
'{^'.self::REGEX_TYPES.'(?:[\s\t].*)?$}sx',
109+
$tokens[$stackPtr + 2]['content'],
110+
$matches
111+
);
100112

101-
$content = 1 === $matchingResult ? $matches['types'] : '';
102-
$endOfContent = substr($tokens[$stackPtr + 2]['content'], strlen($content));
113+
$content = 1 === $matchingResult ? $matches['types'] : '';
114+
$endOfContent = substr($tokens[$stackPtr + 2]['content'], strlen($content));
103115

116+
try {
104117
$suggestedType = $this->getValidTypes($content);
118+
} catch (DeepExitException $exception) {
119+
$phpcsFile->addError(
120+
$exception->getMessage(),
121+
$stackPtr + 2,
122+
'Exception'
123+
);
105124

106-
if ($content !== $suggestedType) {
107-
$fix = $phpcsFile->addFixableError(
108-
'For type-hinting in PHPDocs, use %s instead of %s',
109-
$stackPtr + 2,
110-
'Invalid',
111-
[$suggestedType, $content]
112-
);
125+
return;
126+
}
127+
128+
if ($content !== $suggestedType) {
129+
$fix = $phpcsFile->addFixableError(
130+
'For type-hinting in PHPDocs, use %s instead of %s',
131+
$stackPtr + 2,
132+
'Invalid',
133+
[$suggestedType, $content]
134+
);
113135

114-
if ($fix) {
115-
$phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent);
116-
}
136+
if ($fix) {
137+
$phpcsFile->fixer->replaceToken($stackPtr + 2, $suggestedType.$endOfContent);
117138
}
118139
}
119140
}
@@ -122,26 +143,57 @@ public function process(File $phpcsFile, $stackPtr): void
122143
* @param string $content
123144
*
124145
* @return string
146+
*
147+
* @throws DeepExitException
125148
*/
126149
private function getValidTypes(string $content): string
127150
{
128151
$content = preg_replace('/\s/', '', $content);
129-
$types = $this->getTypes($content);
130152

131-
foreach ($types as $index => $type) {
132-
preg_match('{^'.self::REGEX_TYPES.'$}x', $type, $matches);
153+
$types = [];
154+
$separators = [];
155+
while ('' !== $content && false !== $content) {
156+
preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches);
133157

134-
if (isset($matches['generic']) && '' !== $matches['generic']) {
158+
if (isset($matches['multiple']) && '' !== $matches['multiple']) {
159+
$validType = '('.$this->getValidTypes($matches['mutipleContent']).')';
160+
} elseif (isset($matches['generic']) && '' !== $matches['generic']) {
135161
$validType = $this->getValidGenericType($matches['genericName'], $matches['genericContent']);
136162
} elseif (isset($matches['object']) && '' !== $matches['object']) {
137163
$validType = $this->getValidObjectType($matches['objectContent']);
138164
} else {
139-
$validType = $this->getValidType($type);
165+
$validType = $this->getValidType($matches['type']);
140166
}
141167

142-
$types[$index] = $validType;
168+
$types[] = $validType;
169+
170+
$separators[] = substr($content, strlen($matches['type']), 1);
171+
$content = substr($content, strlen($matches['type']) + 1);
143172
}
144173

174+
// Remove last separator since it's an empty string
175+
array_pop($separators);
176+
177+
$uniqueSeparators = array_unique($separators);
178+
switch (count($uniqueSeparators)) {
179+
case 0:
180+
return implode('', $types);
181+
case 1:
182+
return implode($uniqueSeparators[0], $this->orderTypes($types));
183+
default:
184+
throw new DeepExitException(
185+
'Union and intersection types must be grouped with parenthesis when used in the same expression'
186+
);
187+
}
188+
}
189+
190+
/**
191+
* @param array $types
192+
*
193+
* @return array
194+
*/
195+
private function orderTypes(array $types): array
196+
{
145197
$types = array_unique($types);
146198
usort($types, function ($type1, $type2) {
147199
if ('null' === $type1) {
@@ -155,24 +207,6 @@ private function getValidTypes(string $content): string
155207
return 0;
156208
});
157209

158-
return implode('|', $types);
159-
}
160-
161-
/**
162-
* @param string $content
163-
*
164-
* @return array
165-
*/
166-
private function getTypes(string $content): array
167-
{
168-
$types = [];
169-
while ('' !== $content && false !== $content) {
170-
preg_match('{^'.self::REGEX_TYPES.'$}x', $content, $matches);
171-
172-
$types[] = $matches['type'];
173-
$content = substr($content, strlen($matches['type']) + 1);
174-
}
175-
176210
return $types;
177211
}
178212

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
11
<?php
22

3-
echo (bool) $a;
4-
echo (int) $a;
5-
echo (float) $a;
6-
echo ( float ) $a;
7-
8-
/**
9-
* @param bool $a
10-
* @param int $b
11-
* @param float $c
12-
*
13-
* @return bool
14-
* @return int
15-
* @return float
16-
*/
17-
18-
/** @var bool $a */
19-
/** @var int $b */
20-
/** @var float $c */
21-
223
// Only PHPDoc comments are checked
234
/* @var boolean $a */
245

@@ -63,3 +44,9 @@ echo ( float ) $a;
6344
* @param array{integer, integer}
6445
* @param array{foo: integer, bar: string}
6546
*/
47+
48+
/**
49+
* @param (integer|string)|bool
50+
* @param (integer&string)|bool
51+
* @param integer&string|bool
52+
*/

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.inc.fixed

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
11
<?php
22

3-
echo (bool) $a;
4-
echo (int) $a;
5-
echo (float) $a;
6-
echo ( float ) $a;
7-
8-
/**
9-
* @param bool $a
10-
* @param int $b
11-
* @param float $c
12-
*
13-
* @return bool
14-
* @return int
15-
* @return float
16-
*/
17-
18-
/** @var bool $a */
19-
/** @var int $b */
20-
/** @var float $c */
21-
223
// Only PHPDoc comments are checked
234
/* @var boolean $a */
245

@@ -63,3 +44,9 @@ echo ( float ) $a;
6344
* @param array{int, int}
6445
* @param array{foo: int, bar: string}
6546
*/
47+
48+
/**
49+
* @param (int|string)|bool
50+
* @param (int&string)|bool
51+
* @param integer&string|bool
52+
*/

SymfonyCustom/Tests/NamingConventions/ValidTypeHintUnitTest.php

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,38 @@ class ValidTypeHintUnitTest extends AbstractSniffUnitTest
1919
protected function getErrorList(): array
2020
{
2121
return [
22+
7 => 1,
23+
8 => 1,
24+
9 => 1,
25+
10 => 1,
26+
11 => 1,
27+
13 => 1,
28+
14 => 1,
29+
15 => 1,
30+
16 => 1,
31+
17 => 1,
32+
20 => 1,
33+
21 => 1,
34+
22 => 1,
35+
23 => 1,
36+
24 => 1,
2237
26 => 1,
2338
27 => 1,
2439
28 => 1,
25-
29 => 1,
26-
30 => 1,
40+
31 => 1,
2741
32 => 1,
2842
33 => 1,
29-
34 => 1,
30-
35 => 1,
3143
36 => 1,
44+
37 => 1,
45+
38 => 1,
3246
39 => 1,
3347
40 => 1,
34-
41 => 1,
35-
42 => 1,
3648
43 => 1,
49+
44 => 1,
3750
45 => 1,
38-
46 => 1,
39-
47 => 1,
51+
49 => 1,
4052
50 => 1,
4153
51 => 1,
42-
52 => 1,
43-
55 => 1,
44-
56 => 1,
45-
57 => 1,
46-
58 => 1,
47-
59 => 1,
48-
62 => 1,
49-
63 => 1,
50-
64 => 1,
5154
];
5255
}
5356

0 commit comments

Comments
 (0)