@@ -50,6 +50,16 @@ class DeadMethodRule implements Rule
50
50
51
51
private bool $ reportTransitivelyDeadMethodAsSeparateError ;
52
52
53
+ /**
54
+ * @var array<string, array{string, int}> methodKey => [file, line]
55
+ */
56
+ private array $ deadMethods = [];
57
+
58
+ /**
59
+ * @var array<string, list<string>> caller => callee[]
60
+ */
61
+ private array $ callGraph = [];
62
+
53
63
public function __construct (
54
64
ClassHierarchy $ classHierarchy ,
55
65
bool $ reportTransitivelyDeadMethodAsSeparateError = false
@@ -81,10 +91,6 @@ public function processNode(
81
91
$ methodCallData = $ node ->get (MethodCallCollector::class);
82
92
$ entrypointData = $ node ->get (EntrypointCollector::class);
83
93
84
- /** @var array<string, list<string>> $callGraph caller => callee[] */
85
- $ callGraph = [];
86
- $ deadMethods = [];
87
-
88
94
foreach ($ methodDeclarationData as $ file => $ data ) {
89
95
foreach ($ data as $ typeData ) {
90
96
$ typeName = $ typeData ['name ' ];
@@ -111,7 +117,7 @@ public function processNode(
111
117
112
118
foreach ($ methods as $ methodName => $ methodData ) {
113
119
$ definition = $ this ->getMethodKey ($ typeName , $ methodName );
114
- $ deadMethods [$ definition ] = [$ file , $ methodData ['line ' ]];
120
+ $ this -> deadMethods [$ definition ] = [$ file , $ methodData ['line ' ]];
115
121
}
116
122
}
117
123
@@ -132,7 +138,7 @@ public function processNode(
132
138
$ isWhite = $ call ->caller === null || Method::isUnsupported ($ call ->caller ->methodName );
133
139
134
140
foreach ($ this ->getAlternativeCalleeKeys ($ call ) as $ possibleCalleeKey ) {
135
- $ callGraph [$ callerKey ][] = $ possibleCalleeKey ;
141
+ $ this -> callGraph [$ callerKey ][] = $ possibleCalleeKey ;
136
142
137
143
if ($ isWhite ) {
138
144
$ whiteCallees [] = $ possibleCalleeKey ;
@@ -145,11 +151,7 @@ public function processNode(
145
151
unset($ methodCallData );
146
152
147
153
foreach ($ whiteCallees as $ whiteCalleeKey ) {
148
- unset($ deadMethods [$ whiteCalleeKey ]);
149
-
150
- foreach ($ this ->getTransitiveCalleeKeys ($ whiteCalleeKey , $ callGraph ) as $ subCallKey ) {
151
- unset($ deadMethods [$ subCallKey ]);
152
- }
154
+ $ this ->markTransitiveCallsWhite ($ whiteCalleeKey );
153
155
}
154
156
155
157
foreach ($ entrypointData as $ file => $ entrypointsInFile ) {
@@ -158,34 +160,32 @@ public function processNode(
158
160
$ call = Call::fromString ($ entrypoint );
159
161
160
162
foreach ($ this ->getAlternativeCalleeKeys ($ call ) as $ methodDefinition ) {
161
- unset($ deadMethods [$ methodDefinition ]);
163
+ unset($ this -> deadMethods [$ methodDefinition ]);
162
164
}
163
165
164
- foreach ($ this ->getTransitiveCalleeKeys ($ call ->callee ->toString (), $ callGraph ) as $ subCallKey ) {
165
- unset($ deadMethods [$ subCallKey ]);
166
- }
166
+ $ this ->markTransitiveCallsWhite ($ call ->callee ->toString ());
167
167
}
168
168
}
169
169
}
170
170
171
171
$ errors = [];
172
172
173
173
if ($ this ->reportTransitivelyDeadMethodAsSeparateError ) {
174
- foreach ($ deadMethods as $ deadMethodKey => [$ file , $ line ]) {
174
+ foreach ($ this -> deadMethods as $ deadMethodKey => [$ file , $ line ]) {
175
175
$ errors [] = $ this ->buildError ($ deadMethodKey , [], $ file , $ line );
176
176
}
177
177
178
178
return $ errors ;
179
179
}
180
180
181
- $ deadGroups = $ this ->groupDeadMethods ($ deadMethods , $ callGraph );
181
+ $ deadGroups = $ this ->groupDeadMethods ();
182
182
183
183
foreach ($ deadGroups as $ deadGroupKey => $ deadSubgroupKeys ) {
184
- [$ file , $ line ] = $ deadMethods [$ deadGroupKey ]; // @phpstan-ignore offsetAccess.notFound
184
+ [$ file , $ line ] = $ this -> deadMethods [$ deadGroupKey ]; // @phpstan-ignore offsetAccess.notFound
185
185
$ subGroupMap = [];
186
186
187
187
foreach ($ deadSubgroupKeys as $ deadSubgroupKey ) {
188
- $ subGroupMap [$ deadSubgroupKey ] = $ deadMethods [$ deadSubgroupKey ]; // @phpstan-ignore offsetAccess.notFound
188
+ $ subGroupMap [$ deadSubgroupKey ] = $ this -> deadMethods [$ deadSubgroupKey ]; // @phpstan-ignore offsetAccess.notFound
189
189
}
190
190
191
191
$ errors [] = $ this ->buildError ($ deadGroupKey , $ subGroupMap , $ file , $ line );
@@ -287,55 +287,83 @@ private function getAlternativeCalleeKeys(Call $call): array
287
287
}
288
288
289
289
/**
290
- * @param array<string, list<string>> $callGraph
290
+ * @param array<string, null> $visitedKeys
291
+ */
292
+ private function markTransitiveCallsWhite (string $ callerKey , array $ visitedKeys = []): void
293
+ {
294
+ $ visitedKeys = $ visitedKeys === [] ? [$ callerKey => null ] : $ visitedKeys ;
295
+ $ calleeKeys = $ this ->callGraph [$ callerKey ] ?? [];
296
+
297
+ unset($ this ->deadMethods [$ callerKey ]);
298
+
299
+ foreach ($ calleeKeys as $ calleeKey ) {
300
+ if (array_key_exists ($ calleeKey , $ visitedKeys )) {
301
+ continue ;
302
+ }
303
+
304
+ if (!isset ($ this ->deadMethods [$ calleeKey ])) {
305
+ continue ;
306
+ }
307
+
308
+ $ this ->markTransitiveCallsWhite ($ calleeKey , array_merge ($ visitedKeys , [$ calleeKey => null ]));
309
+ }
310
+ }
311
+
312
+ /**
291
313
* @param array<string, null> $visitedKeys
292
314
* @return list<string>
293
315
*/
294
- private function getTransitiveCalleeKeys (string $ callerKey, array $ callGraph , array $ visitedKeys = []): array
316
+ private function getTransitiveDeadCalls (string $ callerKey , array $ visitedKeys = []): array
295
317
{
296
- $ result = [];
297
318
$ visitedKeys = $ visitedKeys === [] ? [$ callerKey => null ] : $ visitedKeys ;
298
- $ calleeKeys = $ callGraph [$ callerKey ] ?? [];
319
+ $ calleeKeys = $ this ->callGraph [$ callerKey ] ?? [];
320
+
321
+ $ result = [];
299
322
300
323
foreach ($ calleeKeys as $ calleeKey ) {
301
324
if (array_key_exists ($ calleeKey , $ visitedKeys )) {
302
325
continue ;
303
326
}
304
327
328
+ if (!isset ($ this ->deadMethods [$ calleeKey ])) {
329
+ continue ;
330
+ }
331
+
305
332
$ result [] = $ calleeKey ;
306
- $ result = array_merge ($ result , $ this ->getTransitiveCalleeKeys ($ calleeKey , $ callGraph , array_merge ($ visitedKeys , [$ calleeKey => null ])));
333
+
334
+ foreach ($ this ->getTransitiveDeadCalls ($ calleeKey , array_merge ($ visitedKeys , [$ calleeKey => null ])) as $ transitiveDead ) {
335
+ $ result [] = $ transitiveDead ;
336
+ }
307
337
}
308
338
309
339
return $ result ;
310
340
}
311
341
312
342
/**
313
- * @param array<string, mixed> $deadMethods
314
- * @param array<string, list<string>> $callGraph
315
343
* @return array<string, list<string>>
316
344
*/
317
- private function groupDeadMethods (array $ deadMethods , array $ callGraph ): array
345
+ private function groupDeadMethods (): array
318
346
{
319
347
$ deadGroups = [];
320
348
321
349
/** @var array<string, true> $deadMethodsWithCaller */
322
350
$ deadMethodsWithCaller = [];
323
351
324
- foreach ($ callGraph as $ caller => $ callees ) {
325
- if (!array_key_exists ($ caller , $ deadMethods )) {
352
+ foreach ($ this -> callGraph as $ caller => $ callees ) {
353
+ if (!array_key_exists ($ caller , $ this -> deadMethods )) {
326
354
continue ;
327
355
}
328
356
329
357
foreach ($ callees as $ callee ) {
330
- if (array_key_exists ($ callee , $ deadMethods )) {
358
+ if (array_key_exists ($ callee , $ this -> deadMethods )) {
331
359
$ deadMethodsWithCaller [$ callee ] = true ;
332
360
}
333
361
}
334
362
}
335
363
336
364
$ methodsGrouped = [];
337
365
338
- foreach ($ deadMethods as $ deadMethodKey => $ _ ) {
366
+ foreach ($ this -> deadMethods as $ deadMethodKey => $ _ ) {
339
367
if (isset ($ methodsGrouped [$ deadMethodKey ])) {
340
368
continue ;
341
369
}
@@ -347,23 +375,21 @@ private function groupDeadMethods(array $deadMethods, array $callGraph): array
347
375
$ deadGroups [$ deadMethodKey ] = [];
348
376
$ methodsGrouped [$ deadMethodKey ] = true ;
349
377
350
- foreach ($ this ->getTransitiveCalleeKeys ($ deadMethodKey , $ callGraph ) as $ transitiveMethodKey ) {
351
- if (!isset ($ deadMethods [$ transitiveMethodKey ])) {
352
- continue ;
353
- }
378
+ $ transitiveMethodKeys = $ this ->getTransitiveDeadCalls ($ deadMethodKey );
354
379
380
+ foreach ($ transitiveMethodKeys as $ transitiveMethodKey ) {
355
381
$ deadGroups [$ deadMethodKey ][] = $ transitiveMethodKey ;
356
382
$ methodsGrouped [$ transitiveMethodKey ] = true ;
357
383
}
358
384
}
359
385
360
386
// now only cycles remain, lets pick group representatives based on first occurrence
361
- foreach ($ deadMethods as $ deadMethodKey => $ _ ) {
387
+ foreach ($ this -> deadMethods as $ deadMethodKey => $ _ ) {
362
388
if (isset ($ methodsGrouped [$ deadMethodKey ])) {
363
389
continue ;
364
390
}
365
391
366
- $ transitiveDeadMethods = $ this ->getTransitiveCalleeKeys ($ deadMethodKey, $ callGraph );
392
+ $ transitiveDeadMethods = $ this ->getTransitiveDeadCalls ($ deadMethodKey );
367
393
368
394
$ deadGroups [$ deadMethodKey ] = []; // TODO provide info to some Tip that those are cycles?
369
395
$ methodsGrouped [$ deadMethodKey ] = true ;
0 commit comments