1: | <?php |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | |
25: | |
26: | namespace Kint; |
27: | |
28: | class CallFinder |
29: | { |
30: | private static $ignore = array( |
31: | T_CLOSE_TAG => true, |
32: | T_COMMENT => true, |
33: | T_DOC_COMMENT => true, |
34: | T_INLINE_HTML => true, |
35: | T_OPEN_TAG => true, |
36: | T_OPEN_TAG_WITH_ECHO => true, |
37: | T_WHITESPACE => true, |
38: | ); |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | |
45: | |
46: | private static $operator = array( |
47: | T_AND_EQUAL => true, |
48: | T_BOOLEAN_AND => true, |
49: | T_BOOLEAN_OR => true, |
50: | T_ARRAY_CAST => true, |
51: | T_BOOL_CAST => true, |
52: | T_CLONE => true, |
53: | T_CONCAT_EQUAL => true, |
54: | T_DEC => true, |
55: | T_DIV_EQUAL => true, |
56: | T_DOUBLE_CAST => true, |
57: | T_INC => true, |
58: | T_INCLUDE => true, |
59: | T_INCLUDE_ONCE => true, |
60: | T_INSTANCEOF => true, |
61: | T_INT_CAST => true, |
62: | T_IS_EQUAL => true, |
63: | T_IS_GREATER_OR_EQUAL => true, |
64: | T_IS_IDENTICAL => true, |
65: | T_IS_NOT_EQUAL => true, |
66: | T_IS_NOT_IDENTICAL => true, |
67: | T_IS_SMALLER_OR_EQUAL => true, |
68: | T_LOGICAL_AND => true, |
69: | T_LOGICAL_OR => true, |
70: | T_LOGICAL_XOR => true, |
71: | T_MINUS_EQUAL => true, |
72: | T_MOD_EQUAL => true, |
73: | T_MUL_EQUAL => true, |
74: | T_NEW => true, |
75: | T_OBJECT_CAST => true, |
76: | T_OR_EQUAL => true, |
77: | T_PLUS_EQUAL => true, |
78: | T_REQUIRE => true, |
79: | T_REQUIRE_ONCE => true, |
80: | T_SL => true, |
81: | T_SL_EQUAL => true, |
82: | T_SR => true, |
83: | T_SR_EQUAL => true, |
84: | T_STRING_CAST => true, |
85: | T_UNSET_CAST => true, |
86: | T_XOR_EQUAL => true, |
87: | '!' => true, |
88: | '%' => true, |
89: | '&' => true, |
90: | '*' => true, |
91: | '+' => true, |
92: | '-' => true, |
93: | '.' => true, |
94: | '/' => true, |
95: | ':' => true, |
96: | '<' => true, |
97: | '=' => true, |
98: | '>' => true, |
99: | '?' => true, |
100: | '^' => true, |
101: | '|' => true, |
102: | '~' => true, |
103: | ); |
104: | |
105: | private static $strip = array( |
106: | '(' => true, |
107: | ')' => true, |
108: | '[' => true, |
109: | ']' => true, |
110: | '{' => true, |
111: | '}' => true, |
112: | T_OBJECT_OPERATOR => true, |
113: | T_DOUBLE_COLON => true, |
114: | T_NS_SEPARATOR => true, |
115: | ); |
116: | |
117: | public static function getFunctionCalls($source, $line, $function) |
118: | { |
119: | static $up = array( |
120: | '(' => true, |
121: | '[' => true, |
122: | '{' => true, |
123: | T_CURLY_OPEN => true, |
124: | T_DOLLAR_OPEN_CURLY_BRACES => true, |
125: | ); |
126: | static $down = array( |
127: | ')' => true, |
128: | ']' => true, |
129: | '}' => true, |
130: | ); |
131: | static $modifiers = array( |
132: | '!' => true, |
133: | '@' => true, |
134: | '~' => true, |
135: | '+' => true, |
136: | '-' => true, |
137: | ); |
138: | static $identifier = array( |
139: | T_DOUBLE_COLON => true, |
140: | T_STRING => true, |
141: | T_NS_SEPARATOR => true, |
142: | ); |
143: | |
144: | if (KINT_PHP56) { |
145: | self::$operator[T_POW] = true; |
146: | self::$operator[T_POW_EQUAL] = true; |
147: | } |
148: | |
149: | if (KINT_PHP70) { |
150: | self::$operator[T_SPACESHIP] = true; |
151: | } |
152: | |
153: | if (KINT_PHP74) { |
154: | self::$operator[T_COALESCE_EQUAL] = true; |
155: | } |
156: | |
157: | $tokens = \token_get_all($source); |
158: | $cursor = 1; |
159: | $function_calls = array(); |
160: | |
161: | $prev_tokens = array(null, null, null); |
162: | |
163: | if (\is_array($function)) { |
164: | $class = \explode('\\', $function[0]); |
165: | $class = \strtolower(\end($class)); |
166: | $function = \strtolower($function[1]); |
167: | } else { |
168: | $class = null; |
169: | $function = \strtolower($function); |
170: | } |
171: | |
172: | |
173: | foreach ($tokens as $index => $token) { |
174: | if (!\is_array($token)) { |
175: | continue; |
176: | } |
177: | |
178: | |
179: | |
180: | |
181: | $cursor += \substr_count($token[1], "\n"); |
182: | if ($cursor > $line) { |
183: | break; |
184: | } |
185: | |
186: | |
187: | if (isset(self::$ignore[$token[0]])) { |
188: | continue; |
189: | } |
190: | |
191: | $prev_tokens = array($prev_tokens[1], $prev_tokens[2], $token); |
192: | |
193: | |
194: | if (T_STRING !== $token[0] || \strtolower($token[1]) !== $function) { |
195: | continue; |
196: | } |
197: | |
198: | |
199: | $nextReal = self::realTokenIndex($tokens, $index); |
200: | if (!isset($nextReal, $tokens[$nextReal]) || '(' !== $tokens[$nextReal]) { |
201: | continue; |
202: | } |
203: | |
204: | |
205: | if (null === $class) { |
206: | if ($prev_tokens[1] && \in_array($prev_tokens[1][0], array(T_DOUBLE_COLON, T_OBJECT_OPERATOR), true)) { |
207: | continue; |
208: | } |
209: | } else { |
210: | if (!$prev_tokens[1] || T_DOUBLE_COLON !== $prev_tokens[1][0]) { |
211: | continue; |
212: | } |
213: | |
214: | if (!$prev_tokens[0] || T_STRING !== $prev_tokens[0][0] || \strtolower($prev_tokens[0][1]) !== $class) { |
215: | continue; |
216: | } |
217: | } |
218: | |
219: | $inner_cursor = $cursor; |
220: | $depth = 1; |
221: | $offset = $nextReal + 1; |
222: | $instring = false; |
223: | $realtokens = false; |
224: | $paramrealtokens = false; |
225: | $params = array(); |
226: | $shortparam = array(); |
227: | $param_start = $offset; |
228: | |
229: | |
230: | while (isset($tokens[$offset])) { |
231: | $token = $tokens[$offset]; |
232: | |
233: | |
234: | |
235: | if (\is_array($token)) { |
236: | $inner_cursor += \substr_count($token[1], "\n"); |
237: | } |
238: | |
239: | if (!isset(self::$ignore[$token[0]]) && !isset($down[$token[0]])) { |
240: | $paramrealtokens = $realtokens = true; |
241: | } |
242: | |
243: | |
244: | if (isset($up[$token[0]])) { |
245: | if (1 === $depth) { |
246: | $shortparam[] = $token; |
247: | $realtokens = false; |
248: | } |
249: | |
250: | ++$depth; |
251: | } elseif (isset($down[$token[0]])) { |
252: | --$depth; |
253: | |
254: | |
255: | |
256: | if (1 === $depth) { |
257: | if ($realtokens) { |
258: | $shortparam[] = '...'; |
259: | } |
260: | $shortparam[] = $token; |
261: | } |
262: | } elseif ('"' === $token[0]) { |
263: | |
264: | |
265: | if ($instring) { |
266: | --$depth; |
267: | if (1 === $depth) { |
268: | $shortparam[] = '...'; |
269: | } |
270: | } else { |
271: | ++$depth; |
272: | } |
273: | |
274: | $instring = !$instring; |
275: | |
276: | $shortparam[] = '"'; |
277: | } elseif (1 === $depth) { |
278: | if (',' === $token[0]) { |
279: | $params[] = array( |
280: | 'full' => \array_slice($tokens, $param_start, $offset - $param_start), |
281: | 'short' => $shortparam, |
282: | ); |
283: | $shortparam = array(); |
284: | $paramrealtokens = false; |
285: | $param_start = $offset + 1; |
286: | } elseif (T_CONSTANT_ENCAPSED_STRING === $token[0] && \strlen($token[1]) > 2) { |
287: | $shortparam[] = $token[1][0].'...'.$token[1][0]; |
288: | } else { |
289: | $shortparam[] = $token; |
290: | } |
291: | } |
292: | |
293: | |
294: | if ($depth <= 0) { |
295: | if ($paramrealtokens) { |
296: | $params[] = array( |
297: | 'full' => \array_slice($tokens, $param_start, $offset - $param_start), |
298: | 'short' => $shortparam, |
299: | ); |
300: | } |
301: | |
302: | break; |
303: | } |
304: | |
305: | ++$offset; |
306: | } |
307: | |
308: | |
309: | |
310: | if ($inner_cursor < $line) { |
311: | continue; |
312: | } |
313: | |
314: | |
315: | foreach ($params as &$param) { |
316: | $name = self::tokensFormatted($param['short']); |
317: | $expression = false; |
318: | foreach ($name as $token) { |
319: | if (self::tokenIsOperator($token)) { |
320: | $expression = true; |
321: | break; |
322: | } |
323: | } |
324: | |
325: | $param = array( |
326: | 'name' => self::tokensToString($name), |
327: | 'path' => self::tokensToString(self::tokensTrim($param['full'])), |
328: | 'expression' => $expression, |
329: | ); |
330: | } |
331: | |
332: | |
333: | --$index; |
334: | |
335: | while (isset($tokens[$index])) { |
336: | if (!isset(self::$ignore[$tokens[$index][0]]) && !isset($identifier[$tokens[$index][0]])) { |
337: | break; |
338: | } |
339: | |
340: | --$index; |
341: | } |
342: | |
343: | $mods = array(); |
344: | |
345: | while (isset($tokens[$index])) { |
346: | if (isset(self::$ignore[$tokens[$index][0]])) { |
347: | --$index; |
348: | continue; |
349: | } |
350: | |
351: | if (isset($modifiers[$tokens[$index][0]])) { |
352: | $mods[] = $tokens[$index]; |
353: | --$index; |
354: | continue; |
355: | } |
356: | |
357: | break; |
358: | } |
359: | |
360: | $function_calls[] = array( |
361: | 'parameters' => $params, |
362: | 'modifiers' => $mods, |
363: | ); |
364: | } |
365: | |
366: | return $function_calls; |
367: | } |
368: | |
369: | private static function realTokenIndex(array $tokens, $index) |
370: | { |
371: | ++$index; |
372: | |
373: | while (isset($tokens[$index])) { |
374: | if (!isset(self::$ignore[$tokens[$index][0]])) { |
375: | return $index; |
376: | } |
377: | |
378: | ++$index; |
379: | } |
380: | |
381: | return null; |
382: | } |
383: | |
384: | |
385: | |
386: | |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | |
393: | private static function tokenIsOperator($token) |
394: | { |
395: | return '...' !== $token && isset(self::$operator[$token[0]]); |
396: | } |
397: | |
398: | private static function tokensToString(array $tokens) |
399: | { |
400: | $out = ''; |
401: | |
402: | foreach ($tokens as $token) { |
403: | if (\is_string($token)) { |
404: | $out .= $token; |
405: | } elseif (\is_array($token)) { |
406: | $out .= $token[1]; |
407: | } |
408: | } |
409: | |
410: | return $out; |
411: | } |
412: | |
413: | private static function tokensTrim(array $tokens) |
414: | { |
415: | foreach ($tokens as $index => $token) { |
416: | if (isset(self::$ignore[$token[0]])) { |
417: | unset($tokens[$index]); |
418: | } else { |
419: | break; |
420: | } |
421: | } |
422: | |
423: | $tokens = \array_reverse($tokens); |
424: | |
425: | foreach ($tokens as $index => $token) { |
426: | if (isset(self::$ignore[$token[0]])) { |
427: | unset($tokens[$index]); |
428: | } else { |
429: | break; |
430: | } |
431: | } |
432: | |
433: | return \array_reverse($tokens); |
434: | } |
435: | |
436: | private static function tokensFormatted(array $tokens) |
437: | { |
438: | $space = false; |
439: | |
440: | $tokens = self::tokensTrim($tokens); |
441: | |
442: | $output = array(); |
443: | $last = null; |
444: | |
445: | foreach ($tokens as $index => $token) { |
446: | if (isset(self::$ignore[$token[0]])) { |
447: | if ($space) { |
448: | continue; |
449: | } |
450: | |
451: | $next = $tokens[self::realTokenIndex($tokens, $index)]; |
452: | |
453: | if (isset(self::$strip[$last[0]]) && !self::tokenIsOperator($next)) { |
454: | continue; |
455: | } |
456: | |
457: | if (isset(self::$strip[$next[0]]) && $last && !self::tokenIsOperator($last)) { |
458: | continue; |
459: | } |
460: | |
461: | $token = ' '; |
462: | $space = true; |
463: | } else { |
464: | $space = false; |
465: | $last = $token; |
466: | } |
467: | |
468: | $output[] = $token; |
469: | } |
470: | |
471: | return $output; |
472: | } |
473: | } |
474: | |