10
10
use RecursiveIteratorIterator ;
11
11
use ReflectionClass ;
12
12
use ReflectionException ;
13
+ use ReflectionFunction ;
13
14
use ShipMonk \ComposerDependencyAnalyser \Config \Configuration ;
14
15
use ShipMonk \ComposerDependencyAnalyser \Config \ErrorType ;
15
16
use ShipMonk \ComposerDependencyAnalyser \Exception \InvalidPathException ;
16
17
use ShipMonk \ComposerDependencyAnalyser \Result \AnalysisResult ;
17
18
use ShipMonk \ComposerDependencyAnalyser \Result \SymbolUsage ;
18
19
use UnexpectedValueException ;
19
- use function array_change_key_case ;
20
20
use function array_diff ;
21
21
use function array_filter ;
22
22
use function array_key_exists ;
30
30
use function get_defined_functions ;
31
31
use function in_array ;
32
32
use function is_file ;
33
+ use function is_string ;
33
34
use function str_replace ;
34
35
use function strlen ;
35
36
use function strpos ;
36
37
use function strtolower ;
37
38
use function substr ;
38
39
use function trim ;
39
- use const CASE_LOWER ;
40
40
use const DIRECTORY_SEPARATOR ;
41
41
42
42
class Analyser
@@ -80,6 +80,13 @@ class Analyser
80
80
*/
81
81
private $ ignoredSymbols ;
82
82
83
+ /**
84
+ * function name => path
85
+ *
86
+ * @var array<string, string>
87
+ */
88
+ private $ definedFunctions = [];
89
+
83
90
/**
84
91
* @param array<string, ClassLoader> $classLoaders vendorDir => ClassLoader (e.g. result of \Composer\Autoload\ClassLoader::getRegisteredLoaders())
85
92
* @param array<string, bool> $composerJsonDependencies package name => is dev dependency
@@ -94,7 +101,8 @@ public function __construct(
94
101
$ this ->stopwatch = $ stopwatch ;
95
102
$ this ->config = $ config ;
96
103
$ this ->composerJsonDependencies = $ composerJsonDependencies ;
97
- $ this ->ignoredSymbols = $ this ->getIgnoredSymbols ();
104
+
105
+ $ this ->initExistingSymbols ();
98
106
99
107
foreach ($ classLoaders as $ vendorDir => $ classLoader ) {
100
108
$ this ->classLoaders [$ vendorDir ] = $ classLoader ;
@@ -109,7 +117,8 @@ public function run(): AnalysisResult
109
117
$ this ->stopwatch ->start ();
110
118
111
119
$ scannedFilesCount = 0 ;
112
- $ classmapErrors = [];
120
+ $ unknownClassErrors = [];
121
+ $ unknownFunctionErrors = [];
113
122
$ shadowErrors = [];
114
123
$ devInProdErrors = [];
115
124
$ prodOnlyInDevErrors = [];
@@ -125,59 +134,69 @@ public function run(): AnalysisResult
125
134
foreach ($ this ->getUniqueFilePathsToScan () as $ filePath => $ isDevFilePath ) {
126
135
$ scannedFilesCount ++;
127
136
128
- foreach ($ this ->getUsedSymbolsInFile ($ filePath ) as $ usedSymbol => $ lineNumbers ) {
129
- if (isset ($ this ->ignoredSymbols [strtolower ($ usedSymbol )])) {
130
- continue ;
131
- }
137
+ $ usedSymbolsByKind = $ this ->getUsedSymbolsInFile ($ filePath );
132
138
133
- $ symbolPath = $ this ->getSymbolPath ($ usedSymbol );
139
+ foreach ($ usedSymbolsByKind as $ kind => $ usedSymbols ) {
140
+ foreach ($ usedSymbols as $ usedSymbol => $ lineNumbers ) {
141
+ if (isset ($ this ->ignoredSymbols [$ usedSymbol ])) {
142
+ continue ;
143
+ }
134
144
135
- if ($ symbolPath === null ) {
136
- if (!$ ignoreList ->shouldIgnoreUnknownClass ($ usedSymbol , $ filePath )) {
137
- foreach ($ lineNumbers as $ lineNumber ) {
138
- $ classmapErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber );
145
+ $ symbolPath = $ this ->getSymbolPath ($ usedSymbol , $ kind );
146
+
147
+ if ($ symbolPath === null ) {
148
+ if ($ kind === SymbolKind::CLASSLIKE && !$ ignoreList ->shouldIgnoreUnknownClass ($ usedSymbol , $ filePath )) {
149
+ foreach ($ lineNumbers as $ lineNumber ) {
150
+ $ unknownClassErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
151
+ }
139
152
}
140
- }
141
153
142
- continue ;
143
- }
154
+ if ($ kind === SymbolKind::FUNCTION && !$ ignoreList ->shouldIgnoreUnknownFunction ($ usedSymbol , $ filePath )) {
155
+ foreach ($ lineNumbers as $ lineNumber ) {
156
+ $ unknownFunctionErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
157
+ }
158
+ }
144
159
145
- if (!$ this ->isVendorPath ($ symbolPath )) {
146
- continue ; // local class
147
- }
160
+ continue ;
161
+ }
148
162
149
- $ packageName = $ this ->getPackageNameFromVendorPath ($ symbolPath );
163
+ if (!$ this ->isVendorPath ($ symbolPath )) {
164
+ continue ; // local class
165
+ }
150
166
151
- if (
152
- $ this ->isShadowDependency ($ packageName )
153
- && !$ ignoreList ->shouldIgnoreError (ErrorType::SHADOW_DEPENDENCY , $ filePath , $ packageName )
154
- ) {
155
- foreach ($ lineNumbers as $ lineNumber ) {
156
- $ shadowErrors [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber );
167
+ $ packageName = $ this ->getPackageNameFromVendorPath ($ symbolPath );
168
+
169
+ if (
170
+ $ this ->isShadowDependency ($ packageName )
171
+ && !$ ignoreList ->shouldIgnoreError (ErrorType::SHADOW_DEPENDENCY , $ filePath , $ packageName )
172
+ ) {
173
+ foreach ($ lineNumbers as $ lineNumber ) {
174
+ $ shadowErrors [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
175
+ }
157
176
}
158
- }
159
177
160
- if (
161
- !$ isDevFilePath
162
- && $ this ->isDevDependency ($ packageName )
163
- && !$ ignoreList ->shouldIgnoreError (ErrorType::DEV_DEPENDENCY_IN_PROD , $ filePath , $ packageName )
164
- ) {
165
- foreach ($ lineNumbers as $ lineNumber ) {
166
- $ devInProdErrors [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber );
178
+ if (
179
+ !$ isDevFilePath
180
+ && $ this ->isDevDependency ($ packageName )
181
+ && !$ ignoreList ->shouldIgnoreError (ErrorType::DEV_DEPENDENCY_IN_PROD , $ filePath , $ packageName )
182
+ ) {
183
+ foreach ($ lineNumbers as $ lineNumber ) {
184
+ $ devInProdErrors [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
185
+ }
167
186
}
168
- }
169
187
170
- if (
171
- !$ isDevFilePath
172
- && !$ this ->isDevDependency ($ packageName )
173
- ) {
174
- $ prodPackagesUsedInProdPath [$ packageName ] = true ;
175
- }
188
+ if (
189
+ !$ isDevFilePath
190
+ && !$ this ->isDevDependency ($ packageName )
191
+ ) {
192
+ $ prodPackagesUsedInProdPath [$ packageName ] = true ;
193
+ }
176
194
177
- $ usedPackages [$ packageName ] = true ;
195
+ $ usedPackages [$ packageName ] = true ;
178
196
179
- foreach ($ lineNumbers as $ lineNumber ) {
180
- $ usages [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber );
197
+ foreach ($ lineNumbers as $ lineNumber ) {
198
+ $ usages [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
199
+ }
181
200
}
182
201
}
183
202
}
@@ -189,7 +208,7 @@ public function run(): AnalysisResult
189
208
continue ;
190
209
}
191
210
192
- $ symbolPath = $ this ->getSymbolPath ($ forceUsedSymbol );
211
+ $ symbolPath = $ this ->getSymbolPath ($ forceUsedSymbol, null );
193
212
194
213
if ($ symbolPath === null || !$ this ->isVendorPath ($ symbolPath )) {
195
214
continue ;
@@ -239,7 +258,8 @@ public function run(): AnalysisResult
239
258
$ scannedFilesCount ,
240
259
$ this ->stopwatch ->stop (),
241
260
$ usages ,
242
- $ classmapErrors ,
261
+ $ unknownClassErrors ,
262
+ $ unknownFunctionErrors ,
243
263
$ shadowErrors ,
244
264
$ devInProdErrors ,
245
265
$ prodOnlyInDevErrors ,
@@ -297,7 +317,7 @@ private function getPackageNameFromVendorPath(string $realPath): string
297
317
}
298
318
299
319
/**
300
- * @return array<string, list<int>>
320
+ * @return array<SymbolKind::*, array< string, list<int> >>
301
321
* @throws InvalidPathException
302
322
*/
303
323
private function getUsedSymbolsInFile (string $ filePath ): array
@@ -308,7 +328,7 @@ private function getUsedSymbolsInFile(string $filePath): array
308
328
throw new InvalidPathException ("Unable to get contents of ' $ filePath' " );
309
329
}
310
330
311
- return (new UsedSymbolExtractor ($ code ))->parseUsedClasses ();
331
+ return (new UsedSymbolExtractor ($ code ))->parseUsedSymbols ();
312
332
}
313
333
314
334
/**
@@ -349,8 +369,20 @@ private function isVendorPath(string $realPath): bool
349
369
return false ;
350
370
}
351
371
352
- private function getSymbolPath (string $ symbol ): ?string
372
+ private function getSymbolPath (string $ symbol, ? int $ kind ): ?string
353
373
{
374
+ if ($ kind === SymbolKind::FUNCTION || $ kind === null ) {
375
+ $ lowerSymbol = strtolower ($ symbol );
376
+
377
+ if (isset ($ this ->definedFunctions [$ lowerSymbol ])) {
378
+ return $ this ->definedFunctions [$ lowerSymbol ];
379
+ }
380
+
381
+ if ($ kind === SymbolKind::FUNCTION ) {
382
+ return null ;
383
+ }
384
+ }
385
+
354
386
if (!array_key_exists ($ symbol , $ this ->classmap )) {
355
387
$ path = $ this ->detectFileByClassLoader ($ symbol ) ?? $ this ->detectFileByReflection ($ symbol );
356
388
$ this ->classmap [$ symbol ] = $ path === null
@@ -406,12 +438,9 @@ private function normalizePath(string $filePath): string
406
438
return Path::normalize ($ filePath );
407
439
}
408
440
409
- /**
410
- * @return array<string, true>
411
- */
412
- private function getIgnoredSymbols (): array
441
+ private function initExistingSymbols (): void
413
442
{
414
- $ ignoredSymbols = [
443
+ $ this -> ignoredSymbols = [
415
444
// built-in types
416
445
'bool ' => true ,
417
446
'int ' => true ,
@@ -446,12 +475,19 @@ private function getIgnoredSymbols(): array
446
475
447
476
/** @var string $constantName */
448
477
foreach (get_defined_constants () as $ constantName => $ constantValue ) {
449
- $ ignoredSymbols [$ constantName ] = true ;
478
+ $ this -> ignoredSymbols [$ constantName ] = true ;
450
479
}
451
480
452
481
foreach (get_defined_functions () as $ functionNames ) {
453
482
foreach ($ functionNames as $ functionName ) {
454
- $ ignoredSymbols [$ functionName ] = true ;
483
+ $ reflectionFunction = new ReflectionFunction ($ functionName );
484
+ $ functionFilePath = $ reflectionFunction ->getFileName ();
485
+
486
+ if ($ reflectionFunction ->getExtension () === null && is_string ($ functionFilePath )) {
487
+ $ this ->definedFunctions [$ functionName ] = Path::normalize ($ functionFilePath );
488
+ } else {
489
+ $ this ->ignoredSymbols [$ functionName ] = true ;
490
+ }
455
491
}
456
492
}
457
493
@@ -464,12 +500,10 @@ private function getIgnoredSymbols(): array
464
500
foreach ($ classLikes as $ classLikeNames ) {
465
501
foreach ($ classLikeNames as $ classLikeName ) {
466
502
if ((new ReflectionClass ($ classLikeName ))->getExtension () !== null ) {
467
- $ ignoredSymbols [$ classLikeName ] = true ;
503
+ $ this -> ignoredSymbols [$ classLikeName ] = true ;
468
504
}
469
505
}
470
506
}
471
-
472
- return array_change_key_case ($ ignoredSymbols , CASE_LOWER ); // get_defined_functions returns lowercase functions
473
507
}
474
508
475
509
}
0 commit comments