1616use ShipMonk \PHPStan \DeadCode \Graph \ClassMethodRef ;
1717use ShipMonk \PHPStan \DeadCode \Graph \ClassMethodUsage ;
1818use ShipMonk \PHPStan \DeadCode \Graph \UsageOrigin ;
19- use function array_map ;
2019use function array_merge ;
2120use function explode ;
2221use function in_array ;
2322use function is_array ;
2423use function is_string ;
24+ use function preg_match ;
2525use function sprintf ;
2626use function strpos ;
2727use function substr ;
@@ -81,15 +81,26 @@ public function getUsages(
8181 );
8282 }
8383
84+ $ beforeAfterMethodsFromAttributes = array_merge (
85+ $ this ->getMethodNamesFromAttribute ($ method , BeforeMethods::class),
86+ $ this ->getMethodNamesFromAttribute ($ method , AfterMethods::class),
87+ );
88+
89+ foreach ($ beforeAfterMethodsFromAttributes as $ beforeAfterMethod ) {
90+ $ usages [] = $ this ->createUsage (
91+ $ className ,
92+ $ beforeAfterMethod ,
93+ sprintf ('Before/After method, used by %s ' , $ methodName ),
94+ );
95+ }
96+
8497 if ($ this ->isBenchmarkMethod ($ method )) {
8598 $ usages [] = $ this ->createUsage ($ className , $ methodName , 'Benchmark method ' );
8699 }
87100
88- if (! $ this ->isBeforeOrAfterMethod ($ method , $ className )) {
89- continue ;
101+ if ($ this ->isBeforeOrAfterMethod ($ method , $ className )) {
102+ $ usages [] = $ this -> createUsage ( $ className , $ methodName , ' Before/After method ' ) ;
90103 }
91-
92- $ usages [] = $ this ->createUsage ($ className , $ methodName , 'Before/After method ' );
93104 }
94105
95106 return $ usages ;
@@ -100,6 +111,32 @@ private function isBenchmarkMethod(ReflectionMethod $method): bool
100111 return strpos ($ method ->getName (), 'bench ' ) === 0 ;
101112 }
102113
114+ /**
115+ * @return list<string>
116+ */
117+ private function getMethodNamesFromAttribute (
118+ ReflectionMethod $ method ,
119+ string $ attributeClass
120+ ): array
121+ {
122+ $ result = [];
123+
124+ foreach ($ method ->getAttributes ($ attributeClass ) as $ attribute ) {
125+ $ methods = $ attribute ->getArguments ()[0 ] ?? $ attribute ->getArguments ()['methods ' ] ?? [];
126+ if (!is_array ($ methods )) {
127+ $ methods = [$ methods ];
128+ }
129+
130+ foreach ($ methods as $ methodName ) {
131+ if (is_string ($ methodName )) {
132+ $ result [] = $ methodName ;
133+ }
134+ }
135+ }
136+
137+ return $ result ;
138+ }
139+
103140 private function isBeforeOrAfterMethod (
104141 ReflectionMethod $ method ,
105142 string $ className
@@ -108,7 +145,7 @@ private function isBeforeOrAfterMethod(
108145 $ classReflection = $ method ->getDeclaringClass ();
109146 $ methodName = $ method ->getName ();
110147
111- // Check annotations
148+ // Check class-level annotations
112149 $ docComment = $ classReflection ->getDocComment ();
113150 if ($ docComment !== false ) {
114151 $ beforeMethodsFromAnnotations = $ this ->getBeforeMethodsFromAnnotations ($ docComment );
@@ -119,7 +156,7 @@ private function isBeforeOrAfterMethod(
119156 }
120157 }
121158
122- // Check attributes
159+ // Check class-level attributes
123160 foreach ($ classReflection ->getAttributes (BeforeMethods::class) as $ attribute ) {
124161 $ methods = $ attribute ->getArguments ()[0 ] ?? $ attribute ->getArguments ()['methods ' ] ?? [];
125162 if (!is_array ($ methods )) {
@@ -153,9 +190,12 @@ private function isBeforeOrAfterMethod(
153190 * @param false|string $rawPhpDoc
154191 * @return list<string>
155192 */
156- private function getParamProvidersFromAnnotations ($ rawPhpDoc ): array
193+ private function getMethodNamesFromAnnotation (
194+ $ rawPhpDoc ,
195+ string $ annotationName
196+ ): array
157197 {
158- if ($ rawPhpDoc === false || strpos ($ rawPhpDoc , ' @ParamProviders ' ) === false ) {
198+ if ($ rawPhpDoc === false || strpos ($ rawPhpDoc , $ annotationName ) === false ) {
159199 return [];
160200 }
161201
@@ -164,15 +204,30 @@ private function getParamProvidersFromAnnotations($rawPhpDoc): array
164204
165205 $ result = [];
166206
167- foreach ($ phpDoc ->getTagsByName (' @ParamProviders ' ) as $ tag ) {
207+ foreach ($ phpDoc ->getTagsByName ($ annotationName ) as $ tag ) {
168208 $ value = (string ) $ tag ->value ;
169- // Parse the value which could be like "provideData" or {"provideData", "provideMore"}
170- // For simplicity, we'll extract method names from the string
171- // This is a basic implementation - PhpBench uses simple format like @ParamProviders({"provideData"})
172- $ value = trim ($ value , '{}() ' );
173- $ methods = array_map ('trim ' , explode (', ' , $ value ));
209+
210+ // Extract content from parentheses: @BeforeMethods("setUp") -> "setUp"
211+ // or @BeforeMethods({"setUp", "tearDown"}) -> {"setUp", "tearDown"}
212+ if (preg_match ('/\((.+)\)\s*$/ ' , $ value , $ matches )) {
213+ $ value = $ matches [1 ];
214+ }
215+
216+ $ value = trim ($ value );
217+ $ value = trim ($ value , '" \'' );
218+
219+ // If it's a single method name, add it directly
220+ if (strpos ($ value , ', ' ) === false && strpos ($ value , '{ ' ) === false ) {
221+ $ result [] = $ value ;
222+ continue ;
223+ }
224+
225+ // Handle array format: {"method1", "method2"}
226+ $ value = trim ($ value , '{} ' );
227+ $ methods = explode (', ' , $ value );
174228 foreach ($ methods as $ method ) {
175- $ method = trim ($ method , '\'" ' );
229+ $ method = trim ($ method );
230+ $ method = trim ($ method , '" \'' );
176231 if ($ method !== '' ) {
177232 $ result [] = $ method ;
178233 }
@@ -182,6 +237,15 @@ private function getParamProvidersFromAnnotations($rawPhpDoc): array
182237 return $ result ;
183238 }
184239
240+ /**
241+ * @param false|string $rawPhpDoc
242+ * @return list<string>
243+ */
244+ private function getParamProvidersFromAnnotations ($ rawPhpDoc ): array
245+ {
246+ return $ this ->getMethodNamesFromAnnotation ($ rawPhpDoc , '@ParamProviders ' );
247+ }
248+
185249 /** @return list<string> */
186250 private function getParamProvidersFromAttributes (ReflectionMethod $ method ): array
187251 {
@@ -214,28 +278,7 @@ private function getParamProvidersFromAttributes(ReflectionMethod $method): arra
214278 */
215279 private function getBeforeMethodsFromAnnotations ($ rawPhpDoc ): array
216280 {
217- if ($ rawPhpDoc === false || strpos ($ rawPhpDoc , '@BeforeMethods ' ) === false ) {
218- return [];
219- }
220-
221- $ tokens = new TokenIterator ($ this ->lexer ->tokenize ($ rawPhpDoc ));
222- $ phpDoc = $ this ->phpDocParser ->parse ($ tokens );
223-
224- $ result = [];
225-
226- foreach ($ phpDoc ->getTagsByName ('@BeforeMethods ' ) as $ tag ) {
227- $ value = (string ) $ tag ->value ;
228- $ value = trim ($ value , '{}() ' );
229- $ methods = array_map ('trim ' , explode (', ' , $ value ));
230- foreach ($ methods as $ method ) {
231- $ method = trim ($ method , '\'" ' );
232- if ($ method !== '' ) {
233- $ result [] = $ method ;
234- }
235- }
236- }
237-
238- return $ result ;
281+ return $ this ->getMethodNamesFromAnnotation ($ rawPhpDoc , '@BeforeMethods ' );
239282 }
240283
241284 /**
@@ -244,28 +287,7 @@ private function getBeforeMethodsFromAnnotations($rawPhpDoc): array
244287 */
245288 private function getAfterMethodsFromAnnotations ($ rawPhpDoc ): array
246289 {
247- if ($ rawPhpDoc === false || strpos ($ rawPhpDoc , '@AfterMethods ' ) === false ) {
248- return [];
249- }
250-
251- $ tokens = new TokenIterator ($ this ->lexer ->tokenize ($ rawPhpDoc ));
252- $ phpDoc = $ this ->phpDocParser ->parse ($ tokens );
253-
254- $ result = [];
255-
256- foreach ($ phpDoc ->getTagsByName ('@AfterMethods ' ) as $ tag ) {
257- $ value = (string ) $ tag ->value ;
258- $ value = trim ($ value , '{}() ' );
259- $ methods = array_map ('trim ' , explode (', ' , $ value ));
260- foreach ($ methods as $ method ) {
261- $ method = trim ($ method , '\'" ' );
262- if ($ method !== '' ) {
263- $ result [] = $ method ;
264- }
265- }
266- }
267-
268- return $ result ;
290+ return $ this ->getMethodNamesFromAnnotation ($ rawPhpDoc , '@AfterMethods ' );
269291 }
270292
271293 private function createUsage (
0 commit comments