@@ -129,4 +129,167 @@ public static function tokenName(File $file, int $position): string
129129
130130 return $ file ->getTokens ()[$ namePosition ]['content ' ];
131131 }
132- }
132+
133+ /**
134+ * @param int $start
135+ * @param int $end
136+ * @param File $file
137+ * @param array ...$types
138+ * @return array[]
139+ */
140+ public static function filterTokensByType (
141+ int $ start ,
142+ int $ end ,
143+ File $ file ,
144+ ...$ types
145+ ): array {
146+
147+ return array_filter (
148+ $ file ->getTokens (),
149+ function (array $ token , int $ position ) use ($ start , $ end , $ types ): bool {
150+ return
151+ $ position >= $ start
152+ && $ position <= $ end
153+ && in_array ($ token ['code ' ] ?? '' , $ types , true );
154+ },
155+ ARRAY_FILTER_USE_BOTH
156+ );
157+ }
158+
159+ /**
160+ * @param File $file
161+ * @param int $closurePosition
162+ * @param bool $lookForFilters
163+ * @param bool $lookForActions
164+ * @return bool
165+ */
166+ public static function isHookClosure (
167+ File $ file ,
168+ int $ closurePosition ,
169+ bool $ lookForFilters = true ,
170+ bool $ lookForActions = true
171+ ): bool {
172+
173+ $ tokens = $ file ->getTokens ();
174+ if (($ tokens [$ closurePosition ]['code ' ] ?? '' ) !== T_CLOSURE ) {
175+ return false ;
176+ }
177+
178+ $ lookForComma = $ file ->findPrevious (
179+ [T_WHITESPACE ],
180+ $ closurePosition - 1 ,
181+ null ,
182+ true ,
183+ null ,
184+ true
185+ );
186+
187+ if (!$ lookForComma || ($ tokens [$ lookForComma ]['code ' ] ?? '' ) !== T_COMMA ) {
188+ return false ;
189+ }
190+
191+ $ functionCallOpen = $ file ->findPrevious (
192+ [T_OPEN_PARENTHESIS ],
193+ $ lookForComma - 2 ,
194+ null ,
195+ false ,
196+ null ,
197+ true
198+ );
199+
200+ if (!$ functionCallOpen ) {
201+ return false ;
202+ }
203+
204+ $ functionCall = $ file ->findPrevious (
205+ [T_WHITESPACE ],
206+ $ functionCallOpen - 1 ,
207+ null ,
208+ true ,
209+ null ,
210+ true
211+ );
212+
213+ $ actions = [];
214+ $ lookForFilters and $ actions [] = 'add_filter ' ;
215+ $ lookForActions and $ actions [] = 'add_action ' ;
216+
217+ return in_array (($ tokens [$ functionCall ]['content ' ] ?? '' ), $ actions , true );
218+ }
219+
220+ /**
221+ * @param File $file
222+ * @param int $functionPosition
223+ * @return array
224+ */
225+ public static function functionBoundaries (File $ file , int $ functionPosition ): array
226+ {
227+ $ tokens = $ file ->getTokens ();
228+ $ functionStart = $ tokens [$ functionPosition ]['scope_opener ' ] ?? 0 ;
229+ $ functionEnd = $ tokens [$ functionPosition ]['scope_closer ' ] ?? 0 ;
230+ if (!$ functionStart || !$ functionEnd || $ functionStart >= ($ functionEnd - 1 )) {
231+ return [-1 , -1 ];
232+ }
233+
234+ return [$ functionStart , $ functionEnd ];
235+ }
236+
237+ /**
238+ * @param File $file
239+ * @param int $functionPosition
240+ * @return array
241+ */
242+ public static function countReturns (File $ file , int $ functionPosition ): array
243+ {
244+ list ($ functionStart , $ functionEnd ) = self ::functionBoundaries ($ file , $ functionPosition );
245+ if ($ functionStart < 0 || $ functionEnd <= 0 ) {
246+ return [0 , 0 ];
247+ }
248+
249+ $ returnTokens = self ::filterTokensByType (
250+ $ functionStart ,
251+ $ functionEnd ,
252+ $ file ,
253+ T_RETURN
254+ );
255+
256+ if (!$ returnTokens ) {
257+ return [0 , 0 ];
258+ }
259+
260+ $ nonVoidReturnCount = $ voidReturnCount = 0 ;
261+ $ scopeClosers = [];
262+ foreach ($ returnTokens as $ i => $ token ) {
263+ if ($ scopeClosers && $ i === $ scopeClosers [0 ]) {
264+ array_shift ($ scopeClosers );
265+ }
266+ if ($ token ['type ' ] === 'T_FUNCTION ' ) {
267+ array_unshift ($ scopeClosers , $ token ['scope_closer ' ]);
268+ }
269+ if (!$ scopeClosers ) {
270+ Helpers::isVoidReturn ($ file , $ i ) ? $ voidReturnCount ++ : $ nonVoidReturnCount ++;
271+ }
272+ }
273+
274+ return [$ nonVoidReturnCount , $ voidReturnCount ];
275+ }
276+
277+ /**
278+ * @param File $file
279+ * @param int $returnPosition
280+ * @return bool
281+ */
282+ public static function isVoidReturn (File $ file , int $ returnPosition ): bool
283+ {
284+ $ tokens = $ file ->getTokens ();
285+
286+ if (($ tokens [$ returnPosition ]['code ' ] ?? '' ) !== T_RETURN ) {
287+ return false ;
288+ }
289+
290+ $ returnPosition ++;
291+ $ nextToReturn = $ file ->findNext ([T_WHITESPACE ], $ returnPosition , null , true , null , true );
292+
293+ return $ nextToReturn && ($ tokens [$ nextToReturn ]['type ' ] ?? '' ) === 'T_SEMICOLON ' ;
294+ }
295+ }
0 commit comments