4
4
5
5
namespace SymfonyCustom \Sniffs \NamingConventions ;
6
6
7
+ use PHP_CodeSniffer \Exceptions \DeepExitException ;
7
8
use PHP_CodeSniffer \Files \File ;
8
9
use PHP_CodeSniffer \Sniffs \Sniff ;
9
10
use PHP_CodeSniffer \Util \Common ;
@@ -26,51 +27,59 @@ class ValidTypeHintSniff implements Sniff
26
27
private const REGEX_TYPES = '
27
28
(?<types>
28
29
(?<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
- |
56
30
(?<array>
57
- (?&simple )(?:
31
+ (?¬Array )(?:
58
32
\s*\[\s*\]
59
33
)+
60
34
)
61
35
|
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
+ )
70
79
)
71
80
)
72
81
(?:
73
- \s*\| \s*(?&type)
82
+ \s*[\|&] \s*(?&type)
74
83
)*
75
84
)
76
85
' ;
@@ -91,29 +100,41 @@ public function process(File $phpcsFile, $stackPtr): void
91
100
{
92
101
$ tokens = $ phpcsFile ->getTokens ();
93
102
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
+ );
100
112
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 ));
103
115
116
+ try {
104
117
$ suggestedType = $ this ->getValidTypes ($ content );
118
+ } catch (DeepExitException $ exception ) {
119
+ $ phpcsFile ->addError (
120
+ $ exception ->getMessage (),
121
+ $ stackPtr + 2 ,
122
+ 'Exception '
123
+ );
105
124
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
+ );
113
135
114
- if ($ fix ) {
115
- $ phpcsFile ->fixer ->replaceToken ($ stackPtr + 2 , $ suggestedType .$ endOfContent );
116
- }
136
+ if ($ fix ) {
137
+ $ phpcsFile ->fixer ->replaceToken ($ stackPtr + 2 , $ suggestedType .$ endOfContent );
117
138
}
118
139
}
119
140
}
@@ -122,26 +143,57 @@ public function process(File $phpcsFile, $stackPtr): void
122
143
* @param string $content
123
144
*
124
145
* @return string
146
+ *
147
+ * @throws DeepExitException
125
148
*/
126
149
private function getValidTypes (string $ content ): string
127
150
{
128
151
$ content = preg_replace ('/\s/ ' , '' , $ content );
129
- $ types = $ this ->getTypes ($ content );
130
152
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 );
133
157
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 ' ]) {
135
161
$ validType = $ this ->getValidGenericType ($ matches ['genericName ' ], $ matches ['genericContent ' ]);
136
162
} elseif (isset ($ matches ['object ' ]) && '' !== $ matches ['object ' ]) {
137
163
$ validType = $ this ->getValidObjectType ($ matches ['objectContent ' ]);
138
164
} else {
139
- $ validType = $ this ->getValidType ($ type );
165
+ $ validType = $ this ->getValidType ($ matches [ ' type ' ] );
140
166
}
141
167
142
- $ types [$ index ] = $ validType ;
168
+ $ types [] = $ validType ;
169
+
170
+ $ separators [] = substr ($ content , strlen ($ matches ['type ' ]), 1 );
171
+ $ content = substr ($ content , strlen ($ matches ['type ' ]) + 1 );
143
172
}
144
173
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
+ {
145
197
$ types = array_unique ($ types );
146
198
usort ($ types , function ($ type1 , $ type2 ) {
147
199
if ('null ' === $ type1 ) {
@@ -155,24 +207,6 @@ private function getValidTypes(string $content): string
155
207
return 0 ;
156
208
});
157
209
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
-
176
210
return $ types ;
177
211
}
178
212
0 commit comments