99use PHPStan \BetterReflection \Reflector \Reflector ;
1010use PHPStan \BetterReflection \SourceLocator \Ast \Strategy \NodeToReflection ;
1111use PHPStan \BetterReflection \SourceLocator \Type \SourceLocator ;
12- use PHPStan \Php \PhpVersion ;
1312use PHPStan \Reflection \ConstantNameHelper ;
1413use PHPStan \ShouldNotHappenException ;
1514use function array_key_exists ;
1615use function array_values ;
17- use function count ;
1816use function current ;
19- use function ltrim ;
20- use function php_strip_whitespace ;
21- use function preg_match_all ;
22- use function preg_replace ;
23- use function sprintf ;
2417use function strtolower ;
2518
2619class OptimizedDirectorySourceLocator implements SourceLocator
2720{
2821
29- private PhpFileCleaner $ cleaner ;
30-
31- private string $ extraTypes ;
32-
33- /** @var array<string, string>|null */
34- private ?array $ classToFile = null ;
35-
36- /** @var array<string, string>|null */
37- private ?array $ constantToFile = null ;
38-
39- /** @var array<string, array<int, string>>|null */
40- private ?array $ functionToFiles = null ;
41-
4222 /**
43- * @param string[] $files
23+ * @param array<string, string> $classToFile
24+ * @param array<string, array<int, string>> $functionToFiles
25+ * @param array<string, string> $constantToFile
4426 */
4527 public function __construct (
4628 private FileNodesFetcher $ fileNodesFetcher ,
47- private PhpVersion $ phpVersion ,
48- private array $ files ,
29+ private array $ classToFile ,
30+ private array $ functionToFiles ,
31+ private array $ constantToFile ,
4932 )
5033 {
51- $ this ->extraTypes = $ this ->phpVersion ->supportsEnums () ? '|enum ' : '' ;
52-
53- $ this ->cleaner = new PhpFileCleaner ();
5434 }
5535
5636 public function locateIdentifier (Reflector $ reflector , Identifier $ identifier ): ?Reflection
@@ -141,13 +121,6 @@ private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode
141121
142122 private function findFileByClass (string $ className ): ?string
143123 {
144- if ($ this ->classToFile === null ) {
145- $ this ->init ();
146- if ($ this ->classToFile === null ) {
147- throw new ShouldNotHappenException ();
148- }
149- }
150-
151124 if (!array_key_exists ($ className , $ this ->classToFile )) {
152125 return null ;
153126 }
@@ -157,13 +130,6 @@ private function findFileByClass(string $className): ?string
157130
158131 private function findFileByConstant (string $ constantName ): ?string
159132 {
160- if ($ this ->constantToFile === null ) {
161- $ this ->init ();
162- if ($ this ->constantToFile === null ) {
163- throw new ShouldNotHappenException ();
164- }
165- }
166-
167133 if (!array_key_exists ($ constantName , $ this ->constantToFile )) {
168134 return null ;
169135 }
@@ -176,132 +142,18 @@ private function findFileByConstant(string $constantName): ?string
176142 */
177143 private function findFilesByFunction (string $ functionName ): array
178144 {
179- if ($ this ->functionToFiles === null ) {
180- $ this ->init ();
181- if ($ this ->functionToFiles === null ) {
182- throw new ShouldNotHappenException ();
183- }
184- }
185-
186145 if (!array_key_exists ($ functionName , $ this ->functionToFiles )) {
187146 return [];
188147 }
189148
190149 return $ this ->functionToFiles [$ functionName ];
191150 }
192151
193- private function init (): void
194- {
195- $ classToFile = [];
196- $ constantToFile = [];
197- $ functionToFiles = [];
198- foreach ($ this ->files as $ file ) {
199- $ symbols = $ this ->findSymbols ($ file );
200- foreach ($ symbols ['classes ' ] as $ classInFile ) {
201- $ classToFile [$ classInFile ] = $ file ;
202- }
203- foreach ($ symbols ['constants ' ] as $ constantInFile ) {
204- $ constantToFile [$ constantInFile ] = $ file ;
205- }
206- foreach ($ symbols ['functions ' ] as $ functionInFile ) {
207- if (!array_key_exists ($ functionInFile , $ functionToFiles )) {
208- $ functionToFiles [$ functionInFile ] = [];
209- }
210- $ functionToFiles [$ functionInFile ][] = $ file ;
211- }
212- }
213-
214- $ this ->classToFile = $ classToFile ;
215- $ this ->functionToFiles = $ functionToFiles ;
216- $ this ->constantToFile = $ constantToFile ;
217- }
218-
219- /**
220- * Inspired by Composer\Autoload\ClassMapGenerator::findClasses()
221- * @link https://github.com/composer/composer/blob/45d3e133a4691eccb12e9cd6f9dfd76eddc1906d/src/Composer/Autoload/ClassMapGenerator.php#L216
222- *
223- * @return array{classes: string[], functions: string[], constants: string[]}
224- */
225- private function findSymbols (string $ file ): array
226- {
227- $ contents = @php_strip_whitespace ($ file );
228- if ($ contents === '' ) {
229- return ['classes ' => [], 'functions ' => [], 'constants ' => []];
230- }
231-
232- $ matchResults = (bool ) preg_match_all (sprintf ('{\b(?:(?:class|interface|trait|const|function%s)\s)|(?:define\s*\()}i ' , $ this ->extraTypes ), $ contents , $ matches );
233- if (!$ matchResults ) {
234- return ['classes ' => [], 'functions ' => [], 'constants ' => []];
235- }
236-
237- $ contents = $ this ->cleaner ->clean ($ contents , count ($ matches [0 ]));
238-
239- preg_match_all (sprintf ('{
240- (?:
241- \b(?<![\$:>])(?:
242- (?: (?P<type>class|interface|trait%s) \s++ (?P<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) )
243- | (?: (?P<function>function) \s++ (?:&\s*)? (?P<fname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [&\(] )
244- | (?: (?P<constant>const) \s++ (?P<cname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [^;] )
245- | (?: (?: \\\)? (?P<define>define) \s*+ \( \s*+ [ \'"] (?P<dname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:[ \\\\]{1,2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+) )
246- | (?: (?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*+ [\{;] )
247- )
248- )
249- }ix ' , $ this ->extraTypes ), $ contents , $ matches );
250-
251- $ classes = [];
252- $ functions = [];
253- $ constants = [];
254- $ namespace = '' ;
255-
256- for ($ i = 0 , $ len = count ($ matches ['type ' ]); $ i < $ len ; $ i ++) {
257- if (isset ($ matches ['ns ' ][$ i ]) && $ matches ['ns ' ][$ i ] !== '' ) {
258- $ namespace = preg_replace ('~\s+~ ' , '' , strtolower ($ matches ['nsname ' ][$ i ])) . '\\' ;
259- continue ;
260- }
261-
262- if ($ matches ['function ' ][$ i ] !== '' ) {
263- $ functions [] = strtolower (ltrim ($ namespace . $ matches ['fname ' ][$ i ], '\\' ));
264- continue ;
265- }
266-
267- if ($ matches ['constant ' ][$ i ] !== '' ) {
268- $ constants [] = ConstantNameHelper::normalize (ltrim ($ namespace . $ matches ['cname ' ][$ i ], '\\' ));
269- }
270-
271- if ($ matches ['define ' ][$ i ] !== '' ) {
272- $ constants [] = ConstantNameHelper::normalize ($ matches ['dname ' ][$ i ]);
273- continue ;
274- }
275-
276- $ name = $ matches ['name ' ][$ i ];
277-
278- // skip anon classes extending/implementing
279- if ($ name === 'extends ' || $ name === 'implements ' ) {
280- continue ;
281- }
282-
283- $ classes [] = strtolower (ltrim ($ namespace . $ name , '\\' ));
284- }
285-
286- return [
287- 'classes ' => $ classes ,
288- 'functions ' => $ functions ,
289- 'constants ' => $ constants ,
290- ];
291- }
292-
293152 /**
294153 * @return list<Reflection>
295154 */
296155 public function locateIdentifiersByType (Reflector $ reflector , IdentifierType $ identifierType ): array
297156 {
298- if ($ this ->classToFile === null || $ this ->functionToFiles === null || $ this ->constantToFile === null ) {
299- $ this ->init ();
300- if ($ this ->classToFile === null || $ this ->functionToFiles === null || $ this ->constantToFile === null ) {
301- throw new ShouldNotHappenException ();
302- }
303- }
304-
305157 $ reflections = [];
306158 if ($ identifierType ->isClass ()) {
307159 foreach ($ this ->classToFile as $ file ) {
0 commit comments