88use PHPStan \BetterReflection \Reflector \Reflector ;
99use PHPStan \BetterReflection \SourceLocator \Ast \Strategy \NodeToReflection ;
1010use PHPStan \BetterReflection \SourceLocator \Type \SourceLocator ;
11+ use PHPStan \Php \PhpVersion ;
1112use function array_key_exists ;
1213
1314class OptimizedDirectorySourceLocator implements SourceLocator
1415{
1516
1617 private \PHPStan \Reflection \BetterReflection \SourceLocator \FileNodesFetcher $ fileNodesFetcher ;
1718
19+ private PhpVersion $ phpVersion ;
20+
21+ private PhpFileCleaner $ cleaner ;
22+
1823 /** @var string[] */
1924 private array $ files ;
2025
26+ private string $ extraTypes ;
27+
2128 /** @var array<string, string>|null */
2229 private ?array $ classToFile = null ;
2330
@@ -39,11 +46,24 @@ class OptimizedDirectorySourceLocator implements SourceLocator
3946 */
4047 public function __construct (
4148 FileNodesFetcher $ fileNodesFetcher ,
49+ PhpVersion $ phpVersion ,
4250 array $ files
4351 )
4452 {
4553 $ this ->fileNodesFetcher = $ fileNodesFetcher ;
54+ $ this ->phpVersion = $ phpVersion ;
4655 $ 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 ));
4767 }
4868
4969 public function locateIdentifier (Reflector $ reflector , Identifier $ identifier ): ?Reflection
@@ -197,79 +217,18 @@ private function findSymbols(string $file): array
197217 return ['classes ' => [], 'functions ' => []];
198218 }
199219
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 )) {
201221 return ['classes ' => [], 'functions ' => []];
202222 }
203223
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 ]));
266225
267- preg_match_all ('{
226+ preg_match_all (sprintf ( '{
268227 (?:
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:\-]*+)
270229 | \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*+ [\{;]
271230 )
272- }ix ' , $ contents , $ matches );
231+ }ix ' , $ this -> extraTypes ), $ contents , $ matches );
273232
274233 $ classes = [];
275234 $ functions = [];
0 commit comments