99use ShipMonk \PHPStan \DeadCode \Enum \MemberType ;
1010use ShipMonk \PHPStan \DeadCode \Error \BlackMember ;
1111use ShipMonk \PHPStan \DeadCode \Graph \ClassConstantRef ;
12+ use ShipMonk \PHPStan \DeadCode \Graph \ClassMemberUsage ;
1213use ShipMonk \PHPStan \DeadCode \Graph \ClassMethodRef ;
1314use ShipMonk \PHPStan \DeadCode \Graph \CollectedUsage ;
1415use ShipMonk \PHPStan \DeadCode \Graph \UsageOrigin ;
1516use function array_map ;
1617use function array_sum ;
1718use function count ;
1819use function explode ;
20+ use function ltrim ;
21+ use function next ;
22+ use function preg_replace ;
23+ use function reset ;
1924use function sprintf ;
25+ use function str_repeat ;
2026use function str_replace ;
2127use function strpos ;
2228
@@ -34,7 +40,7 @@ class DebugUsagePrinter
3440 /**
3541 * memberKey => usage info
3642 *
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}>
3844 */
3945 private array $ debugMembers ;
4046
@@ -124,21 +130,28 @@ public function printDebugMemberUsages(Output $output): void
124130 $ output ->writeLineFormatted ("\n<fg=red>Usage debugging information:</> " );
125131
126132 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) ));
128134
129135 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+ }
131150
132- foreach ($ debugMember ['eliminationPath ' ] as $ index => $ eliminationPath ) {
133- $ entrypoint = $ index === 0 ? '(entrypoint) ' : '' ;
134- $ output ->writeLineFormatted (sprintf ('| -> <fg=white>%s</> %s ' , $ eliminationPath , $ entrypoint ));
151+ $ depth ++;
135152 }
136153 }
137154
138- if (isset ($ debugMember ['neverReported ' ])) {
139- $ output ->writeLineFormatted (sprintf ("| \n| <fg=yellow>Is never reported as dead: %s</> " , $ debugMember ['neverReported ' ]));
140- }
141-
142155 if (isset ($ debugMember ['usages ' ])) {
143156 $ output ->writeLineFormatted (sprintf ("| \n| <fg=green>Found %d usages:</> " , count ($ debugMember ['usages ' ])));
144157
@@ -152,12 +165,27 @@ public function printDebugMemberUsages(Output $output): void
152165
153166 $ output ->writeLineFormatted ('' );
154167 }
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</> " );
155172 }
156173
157174 $ output ->writeLineFormatted ('' );
158175 }
159176 }
160177
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+
161189 private function getOriginReference (UsageOrigin $ origin ): string
162190 {
163191 $ file = $ origin ->getFile ();
@@ -201,17 +229,17 @@ public function recordUsage(CollectedUsage $collectedUsage): void
201229 }
202230
203231 /**
204- * @param list <string> $usagePath
232+ * @param array <string, list<ClassMemberUsage>> $eliminationPath
205233 */
206- public function markMemberAsWhite (BlackMember $ blackMember , array $ usagePath ): void
234+ public function markMemberAsWhite (BlackMember $ blackMember , array $ eliminationPath ): void
207235 {
208236 $ memberKey = $ blackMember ->getMember ()->toKey ();
209237
210238 if (!isset ($ this ->debugMembers [$ memberKey ])) {
211239 return ;
212240 }
213241
214- $ this ->debugMembers [$ memberKey ]['eliminationPath ' ] = $ usagePath ;
242+ $ this ->debugMembers [$ memberKey ]['eliminationPath ' ] = $ eliminationPath ;
215243 }
216244
217245 public function markMemberAsNeverReported (BlackMember $ blackMember , string $ reason ): void
@@ -227,7 +255,7 @@ public function markMemberAsNeverReported(BlackMember $blackMember, string $reas
227255
228256 /**
229257 * @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}>
231259 */
232260 private function buildDebugMemberKeys (array $ debugMembers ): array
233261 {
@@ -239,24 +267,25 @@ private function buildDebugMemberKeys(array $debugMembers): array
239267 }
240268
241269 [$ class , $ memberName ] = explode (':: ' , $ debugMember ); // @phpstan-ignore offsetAccess.notFound
270+ $ normalizedClass = ltrim ($ class , '\\' );
242271
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 " );
245274 }
246275
247- $ classReflection = $ this ->reflectionProvider ->getClass ($ class );
276+ $ classReflection = $ this ->reflectionProvider ->getClass ($ normalizedClass );
248277
249278 if ($ classReflection ->hasMethod ($ memberName )) {
250- $ key = ClassMethodRef::buildKey ($ class , $ memberName );
279+ $ key = ClassMethodRef::buildKey ($ normalizedClass , $ memberName );
251280
252281 } elseif ($ classReflection ->hasConstant ($ memberName )) {
253- $ key = ClassConstantRef::buildKey ($ class , $ memberName );
282+ $ key = ClassConstantRef::buildKey ($ normalizedClass , $ memberName );
254283
255284 } elseif ($ classReflection ->hasProperty ($ memberName )) {
256285 throw new LogicException ('Properties are not yet supported ' );
257286
258287 } else {
259- throw new LogicException ("Member $ memberName does not exist in $ class " );
288+ throw new LogicException ("Member $ memberName does not exist in $ normalizedClass " );
260289 }
261290
262291 $ result [$ key ] = [];
0 commit comments