8
8
use PHPStan \BetterReflection \Reflector \Reflector ;
9
9
use PHPStan \BetterReflection \SourceLocator \Ast \Strategy \NodeToReflection ;
10
10
use PHPStan \BetterReflection \SourceLocator \Type \SourceLocator ;
11
+ use PHPStan \Php \PhpVersion ;
11
12
use function array_key_exists ;
12
13
13
14
class OptimizedDirectorySourceLocator implements SourceLocator
14
15
{
15
16
16
17
private \PHPStan \Reflection \BetterReflection \SourceLocator \FileNodesFetcher $ fileNodesFetcher ;
17
18
19
+ private PhpVersion $ phpVersion ;
20
+
21
+ private PhpFileCleaner $ cleaner ;
22
+
18
23
/** @var string[] */
19
24
private array $ files ;
20
25
26
+ private string $ extraTypes ;
27
+
21
28
/** @var array<string, string>|null */
22
29
private ?array $ classToFile = null ;
23
30
@@ -39,11 +46,24 @@ class OptimizedDirectorySourceLocator implements SourceLocator
39
46
*/
40
47
public function __construct (
41
48
FileNodesFetcher $ fileNodesFetcher ,
49
+ PhpVersion $ phpVersion ,
42
50
array $ files
43
51
)
44
52
{
45
53
$ this ->fileNodesFetcher = $ fileNodesFetcher ;
54
+ $ this ->phpVersion = $ phpVersion ;
46
55
$ this ->files = $ files ;
56
+
57
+ $ extraTypes = '' ;
58
+ $ extraTypesArray = [];
59
+ if ($ this ->phpVersion ->supportsEnums ()) {
60
+ $ extraTypes = '|enum ' ;
61
+ $ extraTypesArray [] = 'enum ' ;
62
+ }
63
+
64
+ $ this ->extraTypes = $ extraTypes ;
65
+
66
+ $ this ->cleaner = new PhpFileCleaner (array_merge (['class ' , 'interface ' , 'trait ' , 'function ' ], $ extraTypesArray ));
47
67
}
48
68
49
69
public function locateIdentifier (Reflector $ reflector , Identifier $ identifier ): ?Reflection
@@ -197,79 +217,18 @@ private function findSymbols(string $file): array
197
217
return ['classes ' => [], 'functions ' => []];
198
218
}
199
219
200
- if (!preg_match ( '{\b(?:class|interface|trait|function)\s}i ' , $ contents )) {
220
+ if (!preg_match_all ( sprintf ( '{\b(?:class|interface|trait|function%s )\s}i ' , $ this -> extraTypes ), $ contents, $ matches )) {
201
221
return ['classes ' => [], 'functions ' => []];
202
222
}
203
223
204
- // strip heredocs/nowdocs
205
- $ heredocRegex = '{
206
- # opening heredoc/nowdoc delimiter (word-chars)
207
- <<<[ \t]*+([ \'"]?)(\w++) \\1
208
- # needs to be followed by a newline
209
- (?:\r\n|\n|\r)
210
- # the meat of it, matching line by line until end delimiter
211
- (?:
212
- # a valid line is optional white-space (possessive match) not followed by the end delimiter, then anything goes for the rest of the line
213
- [\t ]*+(?! \\2 \b)[^\r\n]*+
214
- # end of line(s)
215
- [\r\n]++
216
- )*
217
- # end delimiter
218
- [\t ]*+ \\2 (?=\b)
219
- }x ' ;
220
-
221
- // run first assuming the file is valid unicode
222
- $ contentWithoutHeredoc = preg_replace ($ heredocRegex . 'u ' , 'null ' , $ contents );
223
- if ($ contentWithoutHeredoc === null ) {
224
- // run again without unicode support if the file failed to be parsed
225
- $ contents = preg_replace ($ heredocRegex , 'null ' , $ contents );
226
- } else {
227
- $ contents = $ contentWithoutHeredoc ;
228
- }
229
- unset($ contentWithoutHeredoc );
230
-
231
- if ($ contents === null ) {
232
- return ['classes ' => [], 'functions ' => []];
233
- }
234
- // strip strings
235
- $ contents = preg_replace ('{"[^" \\\\]*+( \\\\.[^" \\\\]*+)*+"| \'[^ \'\\\\]*+( \\\\.[^ \'\\\\]*+)*+ \'}s ' , 'null ' , $ contents );
236
- if ($ contents === null ) {
237
- return ['classes ' => [], 'functions ' => []];
238
- }
239
- // strip leading non-php code if needed
240
- if (strpos ($ contents , '<? ' ) !== 0 ) {
241
- $ contents = preg_replace ('{^.+?<\?}s ' , '<? ' , $ contents , 1 , $ replacements );
242
- if ($ contents === null ) {
243
- return ['classes ' => [], 'functions ' => []];
244
- }
245
- if ($ replacements === 0 ) {
246
- return ['classes ' => [], 'functions ' => []];
247
- }
248
- }
249
- // strip non-php blocks in the file
250
- $ contents = preg_replace ('{\?>(?:[^<]++|<(?!\?))*+<\?}s ' , '?><? ' , $ contents );
251
- if ($ contents === null ) {
252
- return ['classes ' => [], 'functions ' => []];
253
- }
254
- // strip trailing non-php code if needed
255
- $ pos = strrpos ($ contents , '?> ' );
256
- if ($ pos !== false && strpos (substr ($ contents , $ pos ), '<? ' ) === false ) {
257
- $ contents = substr ($ contents , 0 , $ pos );
258
- }
259
- // strip comments if short open tags are in the file
260
- if (preg_match ('{(<\?)(?!(php|hh))}i ' , $ contents )) {
261
- $ contents = preg_replace ('{//.* | /\*(?:[^*]++|\*(?!/))*\*/}x ' , '' , $ contents );
262
- if ($ contents === null ) {
263
- return ['classes ' => [], 'functions ' => []];
264
- }
265
- }
224
+ $ contents = $ this ->cleaner ->clean ($ contents , count ($ matches [0 ]));
266
225
267
- preg_match_all ('{
226
+ preg_match_all (sprintf ( '{
268
227
(?:
269
- \b(?<![\$:>])(?P<type>class|interface|trait|function) \s++ (?P<byref>&\s*)? (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
228
+ \b(?<![\$:>])(?P<type>class|interface|trait|function%s ) \s++ (?P<byref>&\s*)? (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
270
229
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+ \\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
271
230
)
272
- }ix ' , $ contents , $ matches );
231
+ }ix ' , $ this -> extraTypes ), $ contents , $ matches );
273
232
274
233
$ classes = [];
275
234
$ functions = [];
0 commit comments