9
9
use ShipMonk \PHPStan \DeadCode \Enum \MemberType ;
10
10
use ShipMonk \PHPStan \DeadCode \Error \BlackMember ;
11
11
use ShipMonk \PHPStan \DeadCode \Graph \ClassConstantRef ;
12
+ use ShipMonk \PHPStan \DeadCode \Graph \ClassMemberUsage ;
12
13
use ShipMonk \PHPStan \DeadCode \Graph \ClassMethodRef ;
13
14
use ShipMonk \PHPStan \DeadCode \Graph \CollectedUsage ;
14
15
use ShipMonk \PHPStan \DeadCode \Graph \UsageOrigin ;
15
16
use function array_map ;
16
17
use function array_sum ;
17
18
use function count ;
18
19
use function explode ;
20
+ use function ltrim ;
21
+ use function next ;
22
+ use function preg_replace ;
23
+ use function reset ;
19
24
use function sprintf ;
25
+ use function str_repeat ;
20
26
use function str_replace ;
21
27
use function strpos ;
22
28
@@ -34,7 +40,7 @@ class DebugUsagePrinter
34
40
/**
35
41
* memberKey => usage info
36
42
*
37
- * @var array<string, array{usages?: list<CollectedUsage>, eliminationPath?: list <string>, neverReported?: string}>
43
+ * @var array<string, array{usages?: list<CollectedUsage>, eliminationPath?: array <string, list<ClassMemberUsage> >, neverReported?: string}>
38
44
*/
39
45
private array $ debugMembers ;
40
46
@@ -124,21 +130,28 @@ public function printDebugMemberUsages(Output $output): void
124
130
$ output ->writeLineFormatted ("\n<fg=red>Usage debugging information:</> " );
125
131
126
132
foreach ($ this ->debugMembers as $ memberKey => $ debugMember ) {
127
- $ output ->writeLineFormatted (sprintf ("\n<fg=cyan>%s</> " , $ memberKey ));
133
+ $ output ->writeLineFormatted (sprintf ("\n<fg=cyan>%s</> " , $ this -> prettyMemberKey ( $ memberKey) ));
128
134
129
135
if (isset ($ debugMember ['eliminationPath ' ])) {
130
- $ output ->writeLineFormatted ("| \n| Elimination path: " );
136
+ $ output ->writeLineFormatted ("| \n| <fg=green>Elimination path:</> " );
137
+ $ depth = 0 ;
138
+
139
+ foreach ($ debugMember ['eliminationPath ' ] as $ fragmentKey => $ fragmentUsages ) {
140
+ $ indent = $ depth === 0 ? '<fg=gray>entrypoint</> ' : ' ' . str_repeat (' ' , $ depth ) . '<fg=gray>calls</> ' ;
141
+ $ nextFragmentUsages = next ($ debugMember ['eliminationPath ' ]);
142
+ $ nextFragmentFirstUsage = $ nextFragmentUsages !== false ? reset ($ nextFragmentUsages ) : null ;
143
+ $ nextFragmentFirstUsageOrigin = $ nextFragmentFirstUsage instanceof ClassMemberUsage ? $ nextFragmentFirstUsage ->getOrigin () : null ;
144
+
145
+ if ($ nextFragmentFirstUsageOrigin === null ) {
146
+ $ output ->writeLineFormatted (sprintf ('| %s<fg=white>%s</> ' , $ indent , $ this ->prettyMemberKey ($ fragmentKey )));
147
+ } else {
148
+ $ output ->writeLineFormatted (sprintf ('| %s<fg=white>%s</> (%s) ' , $ indent , $ this ->prettyMemberKey ($ fragmentKey ), $ this ->getOriginReference ($ nextFragmentFirstUsageOrigin )));
149
+ }
131
150
132
- foreach ($ debugMember ['eliminationPath ' ] as $ index => $ eliminationPath ) {
133
- $ entrypoint = $ index === 0 ? '(entrypoint) ' : '' ;
134
- $ output ->writeLineFormatted (sprintf ('| -> <fg=white>%s</> %s ' , $ eliminationPath , $ entrypoint ));
151
+ $ depth ++;
135
152
}
136
153
}
137
154
138
- if (isset ($ debugMember ['neverReported ' ])) {
139
- $ output ->writeLineFormatted (sprintf ("| \n| <fg=yellow>Is never reported as dead: %s</> " , $ debugMember ['neverReported ' ]));
140
- }
141
-
142
155
if (isset ($ debugMember ['usages ' ])) {
143
156
$ output ->writeLineFormatted (sprintf ("| \n| <fg=green>Found %d usages:</> " , count ($ debugMember ['usages ' ])));
144
157
@@ -152,12 +165,27 @@ public function printDebugMemberUsages(Output $output): void
152
165
153
166
$ output ->writeLineFormatted ('' );
154
167
}
168
+ } elseif (isset ($ debugMember ['neverReported ' ])) {
169
+ $ output ->writeLineFormatted (sprintf ("| \n| <fg=yellow>Is never reported as dead: %s</> " , $ debugMember ['neverReported ' ]));
170
+ } else {
171
+ $ output ->writeLineFormatted ("| \n| <fg=yellow>No usages found</> " );
155
172
}
156
173
157
174
$ output ->writeLineFormatted ('' );
158
175
}
159
176
}
160
177
178
+ private function prettyMemberKey (string $ memberKey ): string
179
+ {
180
+ $ replaced = preg_replace ('/^(m|c)\// ' , '' , $ memberKey );
181
+
182
+ if ($ replaced === null ) {
183
+ throw new LogicException ('Failed to pretty member key ' . $ memberKey );
184
+ }
185
+
186
+ return $ replaced ;
187
+ }
188
+
161
189
private function getOriginReference (UsageOrigin $ origin ): string
162
190
{
163
191
$ file = $ origin ->getFile ();
@@ -201,17 +229,17 @@ public function recordUsage(CollectedUsage $collectedUsage): void
201
229
}
202
230
203
231
/**
204
- * @param list <string> $usagePath
232
+ * @param array <string, list<ClassMemberUsage>> $eliminationPath
205
233
*/
206
- public function markMemberAsWhite (BlackMember $ blackMember , array $ usagePath ): void
234
+ public function markMemberAsWhite (BlackMember $ blackMember , array $ eliminationPath ): void
207
235
{
208
236
$ memberKey = $ blackMember ->getMember ()->toKey ();
209
237
210
238
if (!isset ($ this ->debugMembers [$ memberKey ])) {
211
239
return ;
212
240
}
213
241
214
- $ this ->debugMembers [$ memberKey ]['eliminationPath ' ] = $ usagePath ;
242
+ $ this ->debugMembers [$ memberKey ]['eliminationPath ' ] = $ eliminationPath ;
215
243
}
216
244
217
245
public function markMemberAsNeverReported (BlackMember $ blackMember , string $ reason ): void
@@ -227,7 +255,7 @@ public function markMemberAsNeverReported(BlackMember $blackMember, string $reas
227
255
228
256
/**
229
257
* @param list<string> $debugMembers
230
- * @return array<string, array{usages?: list<CollectedUsage>, eliminationPath?: list <string>, neverReported?: string}>
258
+ * @return array<string, array{usages?: list<CollectedUsage>, eliminationPath?: array <string, list<ClassMemberUsage> >, neverReported?: string}>
231
259
*/
232
260
private function buildDebugMemberKeys (array $ debugMembers ): array
233
261
{
@@ -239,24 +267,25 @@ private function buildDebugMemberKeys(array $debugMembers): array
239
267
}
240
268
241
269
[$ class , $ memberName ] = explode (':: ' , $ debugMember ); // @phpstan-ignore offsetAccess.notFound
270
+ $ normalizedClass = ltrim ($ class , '\\' );
242
271
243
- if (!$ this ->reflectionProvider ->hasClass ($ class )) {
244
- throw new LogicException ("Class $ class does not exist " );
272
+ if (!$ this ->reflectionProvider ->hasClass ($ normalizedClass )) {
273
+ throw new LogicException ("Class $ normalizedClass does not exist " );
245
274
}
246
275
247
- $ classReflection = $ this ->reflectionProvider ->getClass ($ class );
276
+ $ classReflection = $ this ->reflectionProvider ->getClass ($ normalizedClass );
248
277
249
278
if ($ classReflection ->hasMethod ($ memberName )) {
250
- $ key = ClassMethodRef::buildKey ($ class , $ memberName );
279
+ $ key = ClassMethodRef::buildKey ($ normalizedClass , $ memberName );
251
280
252
281
} elseif ($ classReflection ->hasConstant ($ memberName )) {
253
- $ key = ClassConstantRef::buildKey ($ class , $ memberName );
282
+ $ key = ClassConstantRef::buildKey ($ normalizedClass , $ memberName );
254
283
255
284
} elseif ($ classReflection ->hasProperty ($ memberName )) {
256
285
throw new LogicException ('Properties are not yet supported ' );
257
286
258
287
} else {
259
- throw new LogicException ("Member $ memberName does not exist in $ class " );
288
+ throw new LogicException ("Member $ memberName does not exist in $ normalizedClass " );
260
289
}
261
290
262
291
$ result [$ key ] = [];
0 commit comments