Completed
Pull Request — master (#546)
by Richard
09:47
created

Kint_SourceParser   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 432
Duplicated Lines 3.24 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 14
loc 432
rs 5.5244
c 0
b 0
f 0
wmc 74
lcom 1
cbo 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
D getFunctionCalls() 0 240 51
A realTokenIndex() 0 14 3
A tokenIsOperator() 0 4 2
A tokensToString() 0 14 4
B tokensTrim() 14 22 5
D tokensFormatted() 0 35 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Kint_SourceParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Kint_SourceParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
class Kint_SourceParser
4
{
5
    private static $ignore = array(
6
        T_CLOSE_TAG => true,
7
        T_COMMENT => true,
8
        T_DOC_COMMENT => true,
9
        T_INLINE_HTML => true,
10
        T_OPEN_TAG => true,
11
        T_OPEN_TAG_WITH_ECHO => true,
12
        T_WHITESPACE => true,
13
    );
14
15
    /**
16
     * Things we need to do specially for operator tokens:
17
     * - Refuse to strip spaces around them
18
     * - Wrap the access path in parentheses if there
19
     *   are any of these in the final short parameter.
20
     */
21
    private static $operator = array(
22
        T_AND_EQUAL => true,
23
        T_BOOLEAN_AND => true,
24
        T_BOOLEAN_OR => true,
25
        T_ARRAY_CAST => true,
26
        T_BOOL_CAST => true,
27
        T_CLONE => true,
28
        T_CONCAT_EQUAL => true,
29
        T_DEC => true,
30
        T_DIV_EQUAL => true,
31
        T_DOUBLE_CAST => true,
32
        T_INC => true,
33
        T_INCLUDE => true,
34
        T_INCLUDE_ONCE => true,
35
        T_INSTANCEOF => true,
36
        T_INT_CAST => true,
37
        T_IS_EQUAL => true,
38
        T_IS_GREATER_OR_EQUAL => true,
39
        T_IS_IDENTICAL => true,
40
        T_IS_NOT_EQUAL => true,
41
        T_IS_NOT_IDENTICAL => true,
42
        T_IS_SMALLER_OR_EQUAL => true,
43
        T_LOGICAL_AND => true,
44
        T_LOGICAL_OR => true,
45
        T_LOGICAL_XOR => true,
46
        T_MINUS_EQUAL => true,
47
        T_MOD_EQUAL => true,
48
        T_MUL_EQUAL => true,
49
        T_NEW => true,
50
        T_OBJECT_CAST => true,
51
        T_OR_EQUAL => true,
52
        T_PLUS_EQUAL => true,
53
        T_REQUIRE => true,
54
        T_REQUIRE_ONCE => true,
55
        T_SL => true,
56
        T_SL_EQUAL => true,
57
        T_SR => true,
58
        T_SR_EQUAL => true,
59
        T_STRING_CAST => true,
60
        T_UNSET_CAST => true,
61
        T_XOR_EQUAL => true,
62
        '!' => true,
63
        '%' => true,
64
        '&' => true,
65
        '*' => true,
66
        '+' => true,
67
        '-' => true,
68
        '.' => true,
69
        '/' => true,
70
        ':' => true,
71
        '<' => true,
72
        '=' => true,
73
        '>' => true,
74
        '?' => true,
75
        '^' => true,
76
        '|' => true,
77
        '~' => true,
78
    );
79
80
    private static $strip = array(
81
        '(' => true,
82
        ')' => true,
83
        '[' => true,
84
        ']' => true,
85
        '{' => true,
86
        '}' => true,
87
        T_OBJECT_OPERATOR => true,
88
        T_DOUBLE_COLON => true,
89
    );
90
91
    public static function getFunctionCalls($source, $line, $function)
92
    {
93
        static $up = array(
94
            '(' => true,
95
            '[' => true,
96
            '{' => true,
97
            T_CURLY_OPEN => true,
98
            T_DOLLAR_OPEN_CURLY_BRACES => true,
99
        );
100
        static $down = array(
101
            ')' => true,
102
            ']' => true,
103
            '}' => true,
104
        );
105
        static $modifiers = array(
106
            '!',
107
            '@',
108
            '~',
109
            '+',
110
            '-',
111
        );
112
113
        if (KINT_PHP53) {
114
            self::$strip[T_NS_SEPARATOR] = true;
115
        }
116
117
        if (KINT_PHP56) {
118
            self::$operator[T_POW] = true;
119
            self::$operator[T_POW_EQUAL] = true;
120
        }
121
122
        if (KINT_PHP70) {
123
            self::$operator[T_SPACESHIP] = true;
124
        }
125
126
        $tokens = token_get_all($source);
127
        $cursor = 1;
128
        $function_calls = array();
129
        $prev_tokens = array(null, null, null);
130
131
        if (is_array($function)) {
132
            $class = explode('\\', $function[0]);
133
            $class = strtolower(end($class));
134
            $function = strtolower($function[1]);
135
        } else {
136
            $class = null;
137
            $function = strtolower($function);
138
        }
139
140
        // Loop through tokens
141
        foreach ($tokens as $index => $token) {
142
            if (!is_array($token)) {
143
                continue;
144
            }
145
146
            // Count newlines for line number instead of using
147
            // $token[2] since it's not available until 5.2.2
148
            // Also note that certain situations (String tokens after whitespace)
149
            // may not have the correct line number unless you do this manually
150
            $cursor += substr_count($token[1], "\n");
151
            if ($cursor > $line) {
152
                break;
153
            }
154
155
            // Store the last real tokens for later
156
            if (isset(self::$ignore[$token[0]])) {
157
                continue;
158
            } else {
159
                $prev_tokens = array($prev_tokens[1], $prev_tokens[2], $token);
160
            }
161
162
            // Check if it's the right type to be the function we're looking for
163
            if ($token[0] !== T_STRING || strtolower($token[1]) !== $function) {
164
                continue;
165
            }
166
167
            // Check if it's a function call
168
            if ($tokens[self::realTokenIndex($tokens, $index, 1)] !== '(') {
169
                continue;
170
            }
171
172
            // Check if it matches the signature
173
            if ($class === null) {
174
                if ($prev_tokens[1] && in_array($prev_tokens[1][0], array(T_DOUBLE_COLON, T_OBJECT_OPERATOR))) {
175
                    continue;
176
                }
177
            } else {
178
                if (!$prev_tokens[1] || $prev_tokens[1][0] !== T_DOUBLE_COLON) {
179
                    continue;
180
                }
181
182
                if (!$prev_tokens[0] || $prev_tokens[0][0] !== T_STRING || strtolower($prev_tokens[0][1]) !== $class) {
183
                    continue;
184
                }
185
            }
186
187
            $inner_cursor = $cursor;
188
            $depth = 0; // The depth respective to the function call
189
            $offset = 1; // The offset from the function call
190
            $instring = false; // Whether we're in a string or not
191
            $realtokens = false; // Whether the string contains anything meaningful or not
192
            $params = array(); // All our collected parameters
193
            $shortparam = array(); // The short version of the parameter
194
            $param_start = 1; // The distance to the start of the parameter
195
196
            // Loop through the following tokens until the function call ends
197
            while (isset($tokens[$index + $offset])) {
198
                $token = $tokens[$index + $offset];
199
200
                // Ensure that the $inner_cursor is correct and
201
                // that $token is either a T_ constant or a string
202
                if (is_array($token)) {
203
                    $inner_cursor += substr_count($token[1], "\n");
204
                }
205
206
                if (!isset(self::$ignore[$token[0]]) && !isset($down[$token[0]])) {
207
                    $realtokens = true;
208
                }
209
210
                // If it's a token that makes us to up a level, increase the depth
211
                if (isset($up[$token[0]])) {
212
                    // If this is the first paren set the start of the param to just after it
213
                    if ($depth === 0) {
214
                        $param_start = $offset + 1;
215
                    } elseif ($depth === 1) {
216
                        $shortparam[] = $token;
217
                        $realtokens = false;
218
                    }
219
220
                    ++$depth;
221
                } elseif (isset($down[$token[0]])) {
222
                    --$depth;
223
224
                    // If this brings us down to the parameter level, and we've had
225
                    // real tokens since going up, fill the $shortparam with an ellipsis
226
                    if ($depth === 1) {
227
                        if ($realtokens) {
228
                            $shortparam[] = '...';
229
                        }
230
                        $shortparam[] = $token;
231
                    }
232
                } elseif ($token[0] === '"') {
233
                    // Strings use the same symbol for up and down, but we can
234
                    // only ever be inside one string, so just use a bool for that
235
                    if ($instring) {
236
                        --$depth;
237
                        if ($depth === 1) {
238
                            $shortparam[] = '...';
239
                        }
240
                    } else {
241
                        ++$depth;
242
                    }
243
244
                    $instring = !$instring;
245
246
                    $shortparam[] = '"';
247
                } elseif ($depth === 1) {
248
                    if ($token[0] === ',') {
249
                        $params[] = array(
250
                            'full' => array_slice($tokens, $index + $param_start, $offset - $param_start),
251
                            'short' => $shortparam,
252
                        );
253
                        $shortparam = array();
254
                        $param_start = $offset + 1;
255
                    } elseif ($token[0] === T_CONSTANT_ENCAPSED_STRING && strlen($token[1]) > 2) {
256
                        $shortparam[] = $token[1][0].'...'.$token[1][0];
257
                    } else {
258
                        $shortparam[] = $token;
259
                    }
260
                }
261
262
                // Depth has dropped to 0 (So we've hit the closing paren)
263
                if ($depth <= 0) {
264
                    $params[] = array(
265
                        'full' => array_slice($tokens, $index + $param_start, $offset - $param_start),
266
                        'short' => $shortparam,
267
                    );
268
269
                    break;
270
                }
271
272
                ++$offset;
273
            }
274
275
            // If we're not passed (or at) the line at the end
276
            // of the function call, we're too early so skip it
277
            if ($inner_cursor < $line) {
278
                continue;
279
            }
280
281
            // Format the final output parameters
282
            foreach ($params as &$param) {
283
                $name = self::tokensFormatted($param['short']);
284
                $expression = false;
285
                foreach ($name as $token) {
286
                    if (self::tokenIsOperator($token)) {
287
                        $expression = true;
288
                        break;
289
                    }
290
                }
291
292
                $param = array(
293
                    'name' => self::tokensToString($name),
294
                    'path' => self::tokensToString(self::tokensTrim($param['full'])),
295
                    'expression' => $expression,
296
                );
297
            }
298
299
            // Get the modifiers
300
            $mods = array();
301
            --$index;
302
303
            while (isset($tokens[$index])) {
304
                if (isset(self::$ignore[$tokens[$index][0]])) {
305
                    --$index;
306
                    continue;
307
                } elseif (is_array($tokens[$index]) && empty($mods)) {
308
                    if ($tokens[$index][0] === T_DOUBLE_COLON || $tokens[$index][0] === T_STRING || (KINT_PHP53 && $tokens[$index][0] === T_NS_SEPARATOR)) {
309
                        --$index;
310
                        continue;
311
                    } else {
312
                        break;
313
                    }
314
                } elseif (in_array($tokens[$index][0], $modifiers)) {
315
                    $mods[] = $tokens[$index];
316
                    --$index;
317
                    continue;
318
                } else {
319
                    break;
320
                }
321
            }
322
323
            $function_calls[] = array(
324
                'parameters' => $params,
325
                'modifiers' => $mods,
326
            );
327
        }
328
329
        return $function_calls;
330
    }
331
332
    private static function realTokenIndex(array $tokens, $index, $direction)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
333
    {
334
        $index += $direction;
335
336
        while (isset($tokens[$index])) {
337
            if (!isset(self::$ignore[$tokens[$index][0]])) {
338
                return $index;
339
            }
340
341
            $index += $direction;
342
        }
343
344
        return null;
345
    }
346
347
    /**
348
     * We need a separate method to check if tokens are operators because we
349
     * occasionally add "..." to short parameter versions. If we simply check
350
     * for `$token[0]` then "..." will incorrectly match the "." operator.
351
     *
352
     * @param array|string $token The token to check
353
     *
354
     * @return bool
355
     */
356
    private static function tokenIsOperator($token)
357
    {
358
        return $token !== '...' && isset(self::$operator[$token[0]]);
359
    }
360
361
    private static function tokensToString(array $tokens)
362
    {
363
        $out = '';
364
365
        foreach ($tokens as $token) {
366
            if (is_string($token)) {
367
                $out .= $token;
368
            } elseif (is_array($token)) {
369
                $out .= $token[1];
370
            }
371
        }
372
373
        return $out;
374
    }
375
376
    private static function tokensTrim(array $tokens)
377
    {
378 View Code Duplication
        foreach ($tokens as $index => $token) {
379
            if (isset(self::$ignore[$token[0]])) {
380
                unset($tokens[$index]);
381
            } else {
382
                break;
383
            }
384
        }
385
386
        $tokens = array_reverse($tokens);
387
388 View Code Duplication
        foreach ($tokens as $index => $token) {
389
            if (isset(self::$ignore[$token[0]])) {
390
                unset($tokens[$index]);
391
            } else {
392
                break;
393
            }
394
        }
395
396
        return array_reverse($tokens);
397
    }
398
399
    private static function tokensFormatted(array $tokens)
400
    {
401
        $space = false;
402
403
        $tokens = self::tokensTrim($tokens);
404
405
        $output = array();
406
        $last = null;
407
408
        foreach ($tokens as $index => $token) {
409
            if (isset(self::$ignore[$token[0]])) {
410
                if ($space) {
411
                    continue;
412
                }
413
414
                $next = $tokens[self::realTokenIndex($tokens, $index, 1)];
415
416
                if (isset(self::$strip[$last[0]]) && !self::tokenIsOperator($next)) {
417
                    continue;
418
                } elseif (isset(self::$strip[$next[0]]) && $last && !self::tokenIsOperator($last)) {
419
                    continue;
420
                }
421
422
                $token = ' ';
423
                $space = true;
424
            } else {
425
                $space = false;
426
                $last = $token;
427
            }
428
429
            $output[] = $token;
430
        }
431
432
        return $output;
433
    }
434
}
435