45
45
class Analyser
46
46
{
47
47
48
+ /**
49
+ * Those are core PHP extensions, that can never be disabled
50
+ * There are more PHP "core" extensions, that are bundled by default, but PHP can be compiled without them
51
+ * You can check which are added conditionally in https://github.com/php/php-src/tree/master/ext (see config.w32 files)
52
+ */
53
+ private const CORE_EXTENSIONS = [
54
+ 'ext-core ' ,
55
+ 'ext-date ' ,
56
+ 'ext-json ' ,
57
+ 'ext-hash ' ,
58
+ 'ext-pcre ' ,
59
+ 'ext-phar ' ,
60
+ 'ext-reflection ' ,
61
+ 'ext-spl ' ,
62
+ 'ext-random ' ,
63
+ 'ext-standard ' ,
64
+ ];
65
+
48
66
/**
49
67
* @var Stopwatch
50
68
*/
@@ -73,7 +91,7 @@ class Analyser
73
91
private $ classmap = [];
74
92
75
93
/**
76
- * package name => is dev dependency
94
+ * package or ext-* => is dev dependency
77
95
*
78
96
* @var array<string, bool>
79
97
*/
@@ -87,15 +105,22 @@ class Analyser
87
105
private $ ignoredSymbols ;
88
106
89
107
/**
90
- * function name => path
108
+ * custom function name => path
91
109
*
92
110
* @var array<string, string>
93
111
*/
94
112
private $ definedFunctions = [];
95
113
114
+ /**
115
+ * kind => [symbol name => ext-*]
116
+ *
117
+ * @var array<SymbolKind::*, array<string, string>>
118
+ */
119
+ private $ extensionSymbols ;
120
+
96
121
/**
97
122
* @param array<string, ClassLoader> $classLoaders vendorDir => ClassLoader (e.g. result of \Composer\Autoload\ClassLoader::getRegisteredLoaders())
98
- * @param array<string, bool> $composerJsonDependencies package name => is dev dependency
123
+ * @param array<string, bool> $composerJsonDependencies package or ext-* => is dev dependency
99
124
*/
100
125
public function __construct (
101
126
Stopwatch $ stopwatch ,
@@ -129,7 +154,7 @@ public function run(): AnalysisResult
129
154
$ prodOnlyInDevErrors = [];
130
155
$ unusedErrors = [];
131
156
132
- $ usedPackages = [];
157
+ $ usedDependencies = [];
133
158
$ prodPackagesUsedInProdPath = [];
134
159
135
160
$ usages = [];
@@ -149,60 +174,65 @@ public function run(): AnalysisResult
149
174
continue ;
150
175
}
151
176
152
- $ symbolPath = $ this ->getSymbolPath ($ usedSymbol , $ kind );
177
+ if (isset ($ this ->extensionSymbols [$ kind ][$ usedSymbol ])) {
178
+ $ dependencyName = $ this ->extensionSymbols [$ kind ][$ usedSymbol ];
153
179
154
- if ($ symbolPath === null ) {
155
- if ($ kind === SymbolKind::CLASSLIKE && !$ ignoreList ->shouldIgnoreUnknownClass ($ usedSymbol , $ filePath )) {
156
- foreach ($ lineNumbers as $ lineNumber ) {
157
- $ unknownClassErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
180
+ } else {
181
+ $ symbolPath = $ this ->getSymbolPath ($ usedSymbol , $ kind );
182
+
183
+ if ($ symbolPath === null ) {
184
+ if ($ kind === SymbolKind::CLASSLIKE && !$ ignoreList ->shouldIgnoreUnknownClass ($ usedSymbol , $ filePath )) {
185
+ foreach ($ lineNumbers as $ lineNumber ) {
186
+ $ unknownClassErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
187
+ }
158
188
}
159
- }
160
189
161
- if ($ kind === SymbolKind::FUNCTION && !$ ignoreList ->shouldIgnoreUnknownFunction ($ usedSymbol , $ filePath )) {
162
- foreach ($ lineNumbers as $ lineNumber ) {
163
- $ unknownFunctionErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
190
+ if ($ kind === SymbolKind::FUNCTION && !$ ignoreList ->shouldIgnoreUnknownFunction ($ usedSymbol , $ filePath )) {
191
+ foreach ($ lineNumbers as $ lineNumber ) {
192
+ $ unknownFunctionErrors [$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
193
+ }
164
194
}
195
+
196
+ continue ;
165
197
}
166
198
167
- continue ;
168
- }
199
+ if (!$ this ->isVendorPath ($ symbolPath )) {
200
+ continue ; // local class
201
+ }
169
202
170
- if (!$ this ->isVendorPath ($ symbolPath )) {
171
- continue ; // local class
203
+ $ dependencyName = $ this ->getPackageNameFromVendorPath ($ symbolPath );
172
204
}
173
205
174
- $ packageName = $ this ->getPackageNameFromVendorPath ($ symbolPath );
175
-
176
206
if (
177
- $ this ->isShadowDependency ($ packageName )
178
- && !$ ignoreList ->shouldIgnoreError (ErrorType::SHADOW_DEPENDENCY , $ filePath , $ packageName )
207
+ $ this ->isShadowDependency ($ dependencyName )
208
+ && !$ ignoreList ->shouldIgnoreError (ErrorType::SHADOW_DEPENDENCY , $ filePath , $ dependencyName )
179
209
) {
180
210
foreach ($ lineNumbers as $ lineNumber ) {
181
- $ shadowErrors [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
211
+ $ shadowErrors [$ dependencyName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
182
212
}
183
213
}
184
214
185
215
if (
186
216
!$ isDevFilePath
187
- && $ this ->isDevDependency ($ packageName )
188
- && !$ ignoreList ->shouldIgnoreError (ErrorType::DEV_DEPENDENCY_IN_PROD , $ filePath , $ packageName )
217
+ && $ this ->isDevDependency ($ dependencyName )
218
+ && !$ ignoreList ->shouldIgnoreError (ErrorType::DEV_DEPENDENCY_IN_PROD , $ filePath , $ dependencyName )
189
219
) {
190
220
foreach ($ lineNumbers as $ lineNumber ) {
191
- $ devInProdErrors [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
221
+ $ devInProdErrors [$ dependencyName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
192
222
}
193
223
}
194
224
195
225
if (
196
226
!$ isDevFilePath
197
- && !$ this ->isDevDependency ($ packageName )
227
+ && !$ this ->isDevDependency ($ dependencyName )
198
228
) {
199
- $ prodPackagesUsedInProdPath [$ packageName ] = true ;
229
+ $ prodPackagesUsedInProdPath [$ dependencyName ] = true ;
200
230
}
201
231
202
- $ usedPackages [ $ packageName ] = true ;
232
+ $ usedDependencies [ $ dependencyName ] = true ;
203
233
204
234
foreach ($ lineNumbers as $ lineNumber ) {
205
- $ usages [$ packageName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
235
+ $ usages [$ dependencyName ][$ usedSymbol ][] = new SymbolUsage ($ filePath , $ lineNumber , $ kind );
206
236
}
207
237
}
208
238
}
@@ -215,19 +245,31 @@ public function run(): AnalysisResult
215
245
continue ;
216
246
}
217
247
218
- $ symbolPath = $ this ->getSymbolPath ($ forceUsedSymbol , null );
248
+ if (
249
+ isset ($ this ->extensionSymbols [SymbolKind::FUNCTION ][$ forceUsedSymbol ])
250
+ || isset ($ this ->extensionSymbols [SymbolKind::CONSTANT ][$ forceUsedSymbol ])
251
+ || isset ($ this ->extensionSymbols [SymbolKind::CLASSLIKE ][$ forceUsedSymbol ])
252
+ ) {
253
+ $ forceUsedDependency = $ this ->extensionSymbols [SymbolKind::FUNCTION ][$ forceUsedSymbol ]
254
+ ?? $ this ->extensionSymbols [SymbolKind::CONSTANT ][$ forceUsedSymbol ]
255
+ ?? $ this ->extensionSymbols [SymbolKind::CLASSLIKE ][$ forceUsedSymbol ];
256
+ } else {
257
+ $ symbolPath = $ this ->getSymbolPath ($ forceUsedSymbol , null );
258
+
259
+ if ($ symbolPath === null || !$ this ->isVendorPath ($ symbolPath )) {
260
+ continue ;
261
+ }
219
262
220
- if ($ symbolPath === null || !$ this ->isVendorPath ($ symbolPath )) {
221
- continue ;
263
+ $ forceUsedDependency = $ this ->getPackageNameFromVendorPath ($ symbolPath );
222
264
}
223
265
224
- $ forceUsedPackage = $ this ->getPackageNameFromVendorPath ($ symbolPath );
225
- $ usedPackages [$ forceUsedPackage ] = true ;
226
- $ forceUsedPackages [$ forceUsedPackage ] = true ;
266
+ $ usedDependencies [$ forceUsedDependency ] = true ;
267
+ $ forceUsedPackages [$ forceUsedDependency ] = true ;
227
268
}
228
269
229
270
if ($ this ->config ->shouldReportUnusedDevDependencies ()) {
230
271
$ dependenciesForUnusedAnalysis = array_keys ($ this ->composerJsonDependencies );
272
+
231
273
} else {
232
274
$ dependenciesForUnusedAnalysis = array_keys (array_filter ($ this ->composerJsonDependencies , static function (bool $ devDependency ) {
233
275
return !$ devDependency ; // dev deps are typically used only in CI
@@ -236,7 +278,8 @@ public function run(): AnalysisResult
236
278
237
279
$ unusedDependencies = array_diff (
238
280
$ dependenciesForUnusedAnalysis ,
239
- array_keys ($ usedPackages )
281
+ array_keys ($ usedDependencies ),
282
+ self ::CORE_EXTENSIONS
240
283
);
241
284
242
285
foreach ($ unusedDependencies as $ unusedDependency ) {
@@ -252,7 +295,8 @@ public function run(): AnalysisResult
252
295
$ prodDependencies ,
253
296
array_keys ($ prodPackagesUsedInProdPath ),
254
297
array_keys ($ forceUsedPackages ), // we dont know where are those used, lets not report them
255
- $ unusedDependencies
298
+ $ unusedDependencies ,
299
+ self ::CORE_EXTENSIONS
256
300
);
257
301
258
302
foreach ($ prodPackagesUsedOnlyInDev as $ prodPackageUsedOnlyInDev ) {
@@ -340,7 +384,11 @@ private function getUsedSymbolsInFile(string $filePath): array
340
384
throw new InvalidPathException ("Unable to get contents of ' $ filePath' " );
341
385
}
342
386
343
- return (new UsedSymbolExtractor ($ code ))->parseUsedSymbols ();
387
+ return (new UsedSymbolExtractor ($ code ))->parseUsedSymbols (
388
+ array_keys ($ this ->extensionSymbols [SymbolKind::CLASSLIKE ]),
389
+ array_keys ($ this ->extensionSymbols [SymbolKind::FUNCTION ]),
390
+ array_keys ($ this ->extensionSymbols [SymbolKind::CONSTANT ])
391
+ );
344
392
}
345
393
346
394
/**
@@ -485,20 +533,41 @@ private function initExistingSymbols(): void
485
533
'Composer \\Autoload \\ClassLoader ' => true ,
486
534
];
487
535
488
- /** @var string $constantName */
489
- foreach (get_defined_constants () as $ constantName => $ constantValue ) {
490
- $ this ->ignoredSymbols [$ constantName ] = true ;
536
+ /** @var array<string, array<string, mixed>> $definedConstants */
537
+ $ definedConstants = get_defined_constants (true );
538
+ foreach ($ definedConstants as $ constantExtension => $ constants ) {
539
+ foreach ($ constants as $ constantName => $ _ ) {
540
+ if ($ constantExtension === 'user ' ) {
541
+ $ this ->ignoredSymbols [$ constantName ] = true ;
542
+ } else {
543
+ $ extensionName = $ this ->getNormalizedExtensionName ($ constantExtension );
544
+
545
+ if (in_array ($ extensionName , self ::CORE_EXTENSIONS , true )) {
546
+ $ this ->ignoredSymbols [$ constantName ] = true ;
547
+ } else {
548
+ $ this ->extensionSymbols [SymbolKind::CONSTANT ][$ constantName ] = $ extensionName ;
549
+ }
550
+ }
551
+ }
491
552
}
492
553
493
554
foreach (get_defined_functions () as $ functionNames ) {
494
555
foreach ($ functionNames as $ functionName ) {
495
556
$ reflectionFunction = new ReflectionFunction ($ functionName );
496
557
$ functionFilePath = $ reflectionFunction ->getFileName ();
497
558
498
- if ($ reflectionFunction ->getExtension () === null && is_string ($ functionFilePath )) {
499
- $ this ->definedFunctions [$ functionName ] = Path::normalize ($ functionFilePath );
559
+ if ($ reflectionFunction ->getExtension () === null ) {
560
+ if (is_string ($ functionFilePath )) {
561
+ $ this ->definedFunctions [$ functionName ] = Path::normalize ($ functionFilePath );
562
+ }
500
563
} else {
501
- $ this ->ignoredSymbols [$ functionName ] = true ;
564
+ $ extensionName = $ this ->getNormalizedExtensionName ($ reflectionFunction ->getExtension ()->name );
565
+
566
+ if (in_array ($ extensionName , self ::CORE_EXTENSIONS , true )) {
567
+ $ this ->ignoredSymbols [$ functionName ] = true ;
568
+ } else {
569
+ $ this ->extensionSymbols [SymbolKind::FUNCTION ][$ functionName ] = $ extensionName ;
570
+ }
502
571
}
503
572
}
504
573
}
@@ -511,11 +580,24 @@ private function initExistingSymbols(): void
511
580
512
581
foreach ($ classLikes as $ classLikeNames ) {
513
582
foreach ($ classLikeNames as $ classLikeName ) {
514
- if ((new ReflectionClass ($ classLikeName ))->getExtension () !== null ) {
515
- $ this ->ignoredSymbols [$ classLikeName ] = true ;
583
+ $ classReflection = new ReflectionClass ($ classLikeName );
584
+
585
+ if ($ classReflection ->getExtension () !== null ) {
586
+ $ extensionName = $ this ->getNormalizedExtensionName ($ classReflection ->getExtension ()->name );
587
+
588
+ if (in_array ($ extensionName , self ::CORE_EXTENSIONS , true )) {
589
+ $ this ->ignoredSymbols [$ classLikeName ] = true ;
590
+ } else {
591
+ $ this ->extensionSymbols [SymbolKind::CLASSLIKE ][$ classLikeName ] = $ extensionName ;
592
+ }
516
593
}
517
594
}
518
595
}
519
596
}
520
597
598
+ private function getNormalizedExtensionName (string $ extension ): string
599
+ {
600
+ return 'ext- ' . ComposerJson::normalizeExtensionName ($ extension );
601
+ }
602
+
521
603
}
0 commit comments