CallFinder::getFunctionCalls()   F
last analyzed

Complexity

Conditions 49
Paths 48

Size

Total Lines 250
Code Lines 146

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 49
eloc 146
c 0
b 0
f 0
nc 48
nop 3
dl 0
loc 250
rs 3.3333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2013 Jonathan Vollebregt ([email protected]), Rokas Šleinius ([email protected])
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9
 * this software and associated documentation files (the "Software"), to deal in
10
 * the Software without restriction, including without limitation the rights to
11
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12
 * the Software, and to permit persons to whom the Software is furnished to do so,
13
 * subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in all
16
 * copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
     * Things we need to do specially for operator tokens:
42
     * - Refuse to strip spaces around them
43
     * - Wrap the access path in parentheses if there
44
     *   are any of these in the final short parameter.
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
        /** @var array<int, null|array|string> Performance optimization preventing backwards loops */
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
        // Loop through tokens
173
        foreach ($tokens as $index => $token) {
174
            if (!\is_array($token)) {
175
                continue;
176
            }
177
178
            // Count newlines for line number instead of using $token[2]
179
            // since certain situations (String tokens after whitespace) may
180
            // not have the correct line number unless you do this manually
181
            $cursor += \substr_count($token[1], "\n");
182
            if ($cursor > $line) {
183
                break;
184
            }
185
186
            // Store the last real tokens for later
187
            if (isset(self::$ignore[$token[0]])) {
188
                continue;
189
            }
190
191
            $prev_tokens = array($prev_tokens[1], $prev_tokens[2], $token);
192
193
            // Check if it's the right type to be the function we're looking for
194
            if (T_STRING !== $token[0] || \strtolower($token[1]) !== $function) {
195
                continue;
196
            }
197
198
            // Check if it's a function call
199
            $nextReal = self::realTokenIndex($tokens, $index);
200
            if (!isset($nextReal, $tokens[$nextReal]) || '(' !== $tokens[$nextReal]) {
201
                continue;
202
            }
203
204
            // Check if it matches the signature
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; // The depth respective to the function call
221
            $offset = $nextReal + 1; // The start of the function call
222
            $instring = false; // Whether we're in a string or not
223
            $realtokens = false; // Whether the current scope contains anything meaningful or not
224
            $paramrealtokens = false; // Whether the current parameter contains anything meaningful
225
            $params = array(); // All our collected parameters
226
            $shortparam = array(); // The short version of the parameter
227
            $param_start = $offset; // The distance to the start of the parameter
228
229
            // Loop through the following tokens until the function call ends
230
            while (isset($tokens[$offset])) {
231
                $token = $tokens[$offset];
232
233
                // Ensure that the $inner_cursor is correct and
234
                // that $token is either a T_ constant or a string
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
                // If it's a token that makes us to up a level, increase the depth
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
                    // If this brings us down to the parameter level, and we've had
255
                    // real tokens since going up, fill the $shortparam with an ellipsis
256
                    if (1 === $depth) {
257
                        if ($realtokens) {
258
                            $shortparam[] = '...';
259
                        }
260
                        $shortparam[] = $token;
261
                    }
262
                } elseif ('"' === $token[0]) {
263
                    // Strings use the same symbol for up and down, but we can
264
                    // only ever be inside one string, so just use a bool for that
265
                    if ($instring) {
266
                        --$depth;
267
                        if (1 === $depth) {
268
                            $shortparam[] = '...';
269
                        }
270
                    } else {
271
                        ++$depth;
272
                    }
273
274
                    $instring = !$instring;
0 ignored issues
show
introduced by
$instring is of type mixed, thus it always evaluated to false.
Loading history...
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
                // Depth has dropped to 0 (So we've hit the closing paren)
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
            // If we're not passed (or at) the line at the end
309
            // of the function call, we're too early so skip it
310
            if ($inner_cursor < $line) {
311
                continue;
312
            }
313
314
            // Format the final output parameters
315
            foreach ($params as &$param) {
316
                $name = self::tokensFormatted($param['short']);
317
                $expression = false;
318
                foreach ($name as $token) {
0 ignored issues
show
Comprehensibility Bug introduced by
$token is overwriting a variable from outer foreach loop.
Loading history...
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
            // Get the modifiers
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
     * We need a separate method to check if tokens are operators because we
386
     * occasionally add "..." to short parameter versions. If we simply check
387
     * for `$token[0]` then "..." will incorrectly match the "." operator.
388
     *
389
     * @param array|string $token The token to check
390
     *
391
     * @return bool
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)) {
0 ignored issues
show
Bug introduced by
$last of type void is incompatible with the type array|string expected by parameter $token of Kint\CallFinder::tokenIsOperator(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

457
                if (isset(self::$strip[$next[0]]) && $last && !self::tokenIsOperator(/** @scrutinizer ignore-type */ $last)) {
Loading history...
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