GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

AST   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 457
Duplicated Lines 2.41 %

Coupling/Cohesion

Components 1
Dependencies 30

Importance

Changes 0
Metric Value
wmc 72
lcom 1
cbo 30
dl 11
loc 457
rs 1.3043
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A convert() 0 4 1
A getExpressions() 0 4 1
A parseNodes() 0 9 1
B parseNode() 0 19 5
A parseNameNode() 0 10 3
A parseAbsoluteName() 0 4 2
B parseParameterNode() 0 19 6
A parseArgumentNode() 0 7 1
D parseExpressionNode() 0 84 20
A parseArrayNode() 0 15 3
A parseFunctionCallNode() 0 16 3
A parseTernaryNode() 0 8 2
A parseClosureNode() 0 22 3
B parseScalarNode() 0 18 6
B parseStatementNode() 0 20 5
A verifyNotControlStructure() 0 11 2
C parseOperatorNode() 11 42 7

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 AST 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 AST, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Pinq\Parsing\PhpParser;
4
5
use PhpParser\Node;
6
use Pinq\Expressions as O;
7
use Pinq\Expressions\Expression;
8
use Pinq\Expressions\Operators;
9
use Pinq\Parsing\ASTException;
10
11
/**
12
 * Converts the PHP-Parser nodes into the equivalent expression tree.
13
 *
14
 * @author Elliot Levin <[email protected]>
15
 */
16
class AST
17
{
18
    /**
19
     * @var Node[]
20
     */
21
    private $nodes = [];
22
23
    public function __construct(array $nodes)
24
    {
25
        $this->nodes = $nodes;
26
    }
27
28
    /**
29
     * Converts the supplied php parser nodes to an equivalent
30
     * expression tree.
31
     *
32
     * @param Node[] $nodes
33
     *
34
     * @return Expression[]
35
     */
36
    public static function convert(array $nodes)
37
    {
38
        return (new self($nodes))->getExpressions();
39
    }
40
41
    /**
42
     * Parses the nodes into the equivalent expression tree
43
     *
44
     * @return Expression[]
45
     */
46
    public function getExpressions()
47
    {
48
        return $this->parseNodes($this->nodes);
49
    }
50
51
    /**
52
     * @param Node[] $nodes
53
     *
54
     * @return Expression[]
55
     */
56
    private function parseNodes(array $nodes)
57
    {
58
        return array_map(
59
                function ($node) {
60
                    return $this->parseNode($node);
61
                },
62
                $nodes
63
        );
64
    }
65
66
    /**
67
     * @param Node $node
68
     *
69
     * @throws \Pinq\Parsing\ASTException
70
     * @return Expression
71
     */
72
    protected function parseNode(Node $node)
73
    {
74
        switch (true) {
75
            case $node instanceof Node\Stmt:
76
                return $this->parseStatementNode($node);
77
78
            case $node instanceof Node\Expr:
79
                return $this->parseExpressionNode($node);
80
81
            case $node instanceof Node\Param:
82
                return $this->parseParameterNode($node);
83
84
            case $node instanceof Node\Arg:
85
                return $this->parseArgumentNode($node);
86
87
            default:
88
                throw new ASTException('Unsupported node type: %s', get_class($node));
89
        }
90
    }
91
92
    /**
93
     * @param $node
94
     *
95
     * @return Expression
96
     */
97
    final public function parseNameNode($node)
98
    {
99
        if ($node instanceof Node\Name) {
100
            return Expression::value($this->parseAbsoluteName($node));
101
        } elseif (is_string($node)) {
102
            return Expression::value($node);
103
        }
104
105
        return $this->parseNode($node);
106
    }
107
108
    protected function parseAbsoluteName(Node\Name $node)
109
    {
110
        return ($node->isFullyQualified() ? '\\' : '') . (string) $node;
111
    }
112
113
    private function parseParameterNode(Node\Param $node)
114
    {
115
        $type = $node->type;
116
        if ($type !== null) {
117
            $type      = (string) $type;
118
            $lowerType = strtolower($type);
119
            if ($type[0] !== '\\' && $lowerType !== 'array' && $lowerType !== 'callable') {
120
                $type = '\\' . $type;
121
            }
122
        }
123
124
        return Expression::parameter(
125
                $node->name,
126
                $type,
127
                $node->default === null ? null : $this->parseNode($node->default),
128
                $node->byRef,
129
                $node->variadic
130
        );
131
    }
132
133
    private function parseArgumentNode(Node\Arg $node)
134
    {
135
        return Expression::argument(
136
                $this->parseNode($node->value),
137
                $node->unpack
138
        );
139
    }
140
141
    // <editor-fold defaultstate="collapsed" desc="Expression node parsers">
142
143
    public function parseExpressionNode(Node\Expr $node)
144
    {
145
        switch (true) {
146
            case $mappedNode = $this->parseOperatorNode($node):
147
                return $mappedNode;
148
149
            case $node instanceof Node\Scalar
150
                    && $mappedNode = $this->parseScalarNode($node):
151
                return $mappedNode;
152
153
            case $node instanceof Node\Expr\Variable:
154
                return Expression::variable($this->parseNameNode($node->name));
155
156
            case $node instanceof Node\Expr\Array_:
157
                return $this->parseArrayNode($node);
158
159
            case $node instanceof Node\Expr\FuncCall:
160
                return $this->parseFunctionCallNode($node);
161
162
            case $node instanceof Node\Expr\New_:
163
                return Expression::newExpression(
164
                        $this->parseNameNode($node->class),
165
                        $this->parseNodes($node->args)
166
                );
167
168
            case $node instanceof Node\Expr\MethodCall:
169
                return Expression::methodCall(
170
                        $this->parseNode($node->var),
171
                        $this->parseNameNode($node->name),
172
                        $this->parseNodes($node->args)
173
                );
174
175
            case $node instanceof Node\Expr\PropertyFetch:
176
                return Expression::field(
177
                        $this->parseNode($node->var),
178
                        $this->parseNameNode($node->name)
179
                );
180
181
            case $node instanceof Node\Expr\ArrayDimFetch:
182
                return Expression::index(
183
                        $this->parseNode($node->var),
184
                        $node->dim === null ? null : $this->parseNode($node->dim)
185
                );
186
187
            case $node instanceof Node\Expr\ConstFetch:
188
                return Expression::constant($this->parseAbsoluteName($node->name));
189
190
            case $node instanceof Node\Expr\ClassConstFetch:
191
                return Expression::classConstant(
192
                        $this->parseNameNode($node->class),
193
                        $node->name
0 ignored issues
show
Bug introduced by
It seems like $node->name can also be of type object<PhpParser\Node\Expr\Error>; however, Pinq\Expressions\Expression::classConstant() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
194
                );
195
196
            case $node instanceof Node\Expr\StaticCall:
197
                return Expression::staticMethodCall(
198
                        $this->parseNameNode($node->class),
199
                        $this->parseNameNode($node->name),
200
                        $this->parseNodes($node->args)
201
                );
202
203
            case $node instanceof Node\Expr\StaticPropertyFetch:
204
                return Expression::staticField(
205
                        $this->parseNameNode($node->class),
206
                        $this->parseNameNode($node->name)
207
                );
208
209
            case $node instanceof Node\Expr\Ternary:
210
                return $this->parseTernaryNode($node);
211
212
            case $node instanceof Node\Expr\Closure:
213
                return $this->parseClosureNode($node);
214
215
            case $node instanceof Node\Expr\Empty_:
216
                return Expression::emptyExpression($this->parseNode($node->expr));
217
218
            case $node instanceof Node\Expr\Isset_:
219
                return Expression::issetExpression($this->parseNodes($node->vars));
220
221
            default:
222
                throw new ASTException(
223
                        'Cannot parse AST with unknown expression node: %s',
224
                        get_class($node));
225
        }
226
    }
227
228
    private function parseArrayNode(Node\Expr\Array_ $node)
229
    {
230
        $itemExpressions = [];
231
232
        foreach ($node->items as $item) {
233
            //Keys must match
234
            $itemExpressions[] = Expression::arrayItem(
235
                    $item->key === null ? null : $this->parseNode($item->key),
236
                    $this->parseNode($item->value),
237
                    $item->byRef
238
            );
239
        }
240
241
        return Expression::arrayExpression($itemExpressions);
242
    }
243
244
    private function parseFunctionCallNode(Node\Expr\FuncCall $node)
245
    {
246
        $nameExpression = $this->parseNameNode($node->name);
247
248
        if ($nameExpression instanceof O\TraversalExpression || $nameExpression instanceof O\VariableExpression) {
249
            return Expression::invocation(
250
                    $nameExpression,
251
                    $this->parseNodes($node->args)
252
            );
253
        } else {
254
            return Expression::functionCall(
255
                    $nameExpression,
256
                    $this->parseNodes($node->args)
257
            );
258
        }
259
    }
260
261
    private function parseTernaryNode(Node\Expr\Ternary $node)
262
    {
263
        return Expression::ternary(
264
                $this->parseNode($node->cond),
265
                $node->if === null ? null : $this->parseNode($node->if),
266
                $this->parseNode($node->else)
267
        );
268
    }
269
270
    private function parseClosureNode(Node\Expr\Closure $node)
271
    {
272
        $parameterExpressions = [];
273
274
        foreach ($node->params as $parameterNode) {
275
            $parameterExpressions[] = $this->parseParameterNode($parameterNode);
276
        }
277
278
        $usedVariables = [];
279
        foreach ($node->uses as $usedVariable) {
280
            $usedVariables[] = Expression::closureUsedVariable($usedVariable->var, $usedVariable->byRef);
281
        }
282
        $bodyExpressions = $this->parseNodes($node->stmts);
283
284
        return Expression::closure(
285
                $node->byRef,
286
                $node->static,
287
                $parameterExpressions,
288
                $usedVariables,
289
                $bodyExpressions
290
        );
291
    }
292
293
    private function parseScalarNode(Node\Scalar $node)
294
    {
295
        switch (true) {
296
            case $node instanceof Node\Scalar\DNumber:
297
            case $node instanceof Node\Scalar\LNumber:
298
            case $node instanceof Node\Scalar\String_:
299
                return Expression::value($node->value);
300
301
            case $node instanceof Node\Scalar\MagicConst\Line:
302
                return Expression::value($node->getAttribute('startLine'));
303
304
            case $node instanceof Node\Scalar\MagicConst:
305
                return Expression::constant($node->getName());
306
307
            default:
308
                return;
309
        }
310
    }
311
312
    // </editor-fold>
313
314
    // <editor-fold defaultstate="collapsed" desc="Statement node parsers">
315
316
    private function parseStatementNode(Node\Stmt $node)
317
    {
318
        switch (true) {
319
320
            case $node instanceof Node\Stmt\Return_:
321
                return Expression::returnExpression($node->expr !== null ? $this->parseNode($node->expr) : null);
322
323
            case $node instanceof Node\Stmt\Throw_:
324
                return Expression::throwExpression($this->parseNode($node->expr));
325
326
            case $node instanceof Node\Stmt\Unset_:
327
                return Expression::unsetExpression($this->parseNodes($node->vars));
328
329
            default:
330
                $this->verifyNotControlStructure($node);
331
                throw new ASTException(
332
                        'Cannot parse AST with unknown statement node: %s',
333
                        get_class($node));
334
        }
335
    }
336
337
    private static $constructStructureMap = [
338
            'Do'       => ASTException::DO_WHILE_LOOP,
339
            'For'      => ASTException::FOR_LOOP,
340
            'Foreach'  => ASTException::FOREACH_LOOP,
341
            'Goto'     => ASTException::GOTO_STATEMENT,
342
            'If'       => ASTException::IF_STATEMENT,
343
            'Switch'   => ASTException::SWITCH_STATEMENT,
344
            'TryCatch' => ASTException::TRY_CATCH_STATEMENT,
345
            'While'    => ASTException::WHILE_LOOP
346
    ];
347
348
    private function verifyNotControlStructure(Node\Stmt $node)
349
    {
350
        $nodeType = str_replace('Stmt_', '', $node->getType());
351
352
        if (isset(self::$constructStructureMap[$nodeType])) {
353
            throw ASTException::containsControlStructure(
354
                    self::$constructStructureMap[$nodeType],
355
                    $node->getAttribute('startLine')
356
            );
357
        }
358
    }
359
360
    // </editor-fold>
361
362
    // <editor-fold defaultstate="collapsed" desc="Operator node maps">
363
364
    private function parseOperatorNode(Node\Expr $node)
365
    {
366
        $nodeType = str_replace('Expr_', '', $node->getType());
367
        switch (true) {
368
369 View Code Duplication
            case isset(self::$assignOperatorsMap[$nodeType]):
370
                return Expression::assign(
371
                        $this->parseNode($node->var),
372
                        self::$assignOperatorsMap[$nodeType],
373
                        $this->parseNode($node->expr)
374
                );
375
376
            case $node instanceof Node\Expr\Instanceof_:
377
                return Expression::binaryOperation(
378
                        $this->parseNode($node->expr),
379
                        Operators\Binary::IS_INSTANCE_OF,
380
                        $this->parseNameNode($node->class)
381
                );
382
383
            case isset(self::$binaryOperatorsMap[$nodeType]):
384
                return Expression::binaryOperation(
385
                        $this->parseNode($node->left),
386
                        self::$binaryOperatorsMap[$nodeType],
387
                        $this->parseNode($node->right)
388
                );
389
390 View Code Duplication
            case isset(self::$unaryOperatorsMap[$nodeType]):
391
                return Expression::unaryOperation(
392
                        self::$unaryOperatorsMap[$nodeType],
393
                        $this->parseNode(isset($node->expr) ? $node->expr : $node->var)
394
                );
395
396
            case isset(self::$castOperatorMap[$nodeType]):
397
                return Expression::cast(
398
                        self::$castOperatorMap[$nodeType],
399
                        $this->parseNode($node->expr)
400
                );
401
402
            default:
403
                return null;
404
        }
405
    }
406
407
    private static $unaryOperatorsMap = [
408
            'BitwiseNot' => Operators\Unary::BITWISE_NOT,
409
            'BooleanNot' => Operators\Unary::NOT,
410
            'PostInc'    => Operators\Unary::INCREMENT,
411
            'PostDec'    => Operators\Unary::DECREMENT,
412
            'PreInc'     => Operators\Unary::PRE_INCREMENT,
413
            'PreDec'     => Operators\Unary::PRE_DECREMENT,
414
            'UnaryMinus' => Operators\Unary::NEGATION,
415
            'UnaryPlus'  => Operators\Unary::PLUS
416
    ];
417
418
    private static $castOperatorMap = [
419
            'Cast_Array'  => Operators\Cast::ARRAY_CAST,
420
            'Cast_Bool'   => Operators\Cast::BOOLEAN,
421
            'Cast_Double' => Operators\Cast::DOUBLE,
422
            'Cast_Int'    => Operators\Cast::INTEGER,
423
            'Cast_Object' => Operators\Cast::OBJECT,
424
            'Cast_String' => Operators\Cast::STRING
425
    ];
426
427
    private static $binaryOperatorsMap = [
428
            'BinaryOp_BitwiseAnd'     => Operators\Binary::BITWISE_AND,
429
            'BinaryOp_BitwiseOr'      => Operators\Binary::BITWISE_OR,
430
            'BinaryOp_BitwiseXor'     => Operators\Binary::BITWISE_XOR,
431
            'BinaryOp_ShiftLeft'      => Operators\Binary::SHIFT_LEFT,
432
            'BinaryOp_ShiftRight'     => Operators\Binary::SHIFT_RIGHT,
433
            'BinaryOp_BooleanAnd'     => Operators\Binary::LOGICAL_AND,
434
            'BinaryOp_BooleanOr'      => Operators\Binary::LOGICAL_OR,
435
            'BinaryOp_LogicalAnd'     => Operators\Binary::LOGICAL_AND,
436
            'BinaryOp_LogicalOr'      => Operators\Binary::LOGICAL_OR,
437
            'BinaryOp_Plus'           => Operators\Binary::ADDITION,
438
            'BinaryOp_Minus'          => Operators\Binary::SUBTRACTION,
439
            'BinaryOp_Mul'            => Operators\Binary::MULTIPLICATION,
440
            'BinaryOp_Div'            => Operators\Binary::DIVISION,
441
            'BinaryOp_Mod'            => Operators\Binary::MODULUS,
442
            'BinaryOp_Pow'            => Operators\Binary::POWER,
443
            'BinaryOp_Concat'         => Operators\Binary::CONCATENATION,
444
            'BinaryOp_Equal'          => Operators\Binary::EQUALITY,
445
            'BinaryOp_Identical'      => Operators\Binary::IDENTITY,
446
            'BinaryOp_NotEqual'       => Operators\Binary::INEQUALITY,
447
            'BinaryOp_NotIdentical'   => Operators\Binary::NOT_IDENTICAL,
448
            'BinaryOp_Smaller'        => Operators\Binary::LESS_THAN,
449
            'BinaryOp_SmallerOrEqual' => Operators\Binary::LESS_THAN_OR_EQUAL_TO,
450
            'BinaryOp_Greater'        => Operators\Binary::GREATER_THAN,
451
            'BinaryOp_GreaterOrEqual' => Operators\Binary::GREATER_THAN_OR_EQUAL_TO
452
    ];
453
454
    private static $assignOperatorsMap = [
455
            'Assign'              => Operators\Assignment::EQUAL,
456
            'AssignRef'           => Operators\Assignment::EQUAL_REFERENCE,
457
            'AssignOp_BitwiseAnd' => Operators\Assignment::BITWISE_AND,
458
            'AssignOp_BitwiseOr'  => Operators\Assignment::BITWISE_OR,
459
            'AssignOp_BitwiseXor' => Operators\Assignment::BITWISE_XOR,
460
            'AssignOp_Concat'     => Operators\Assignment::CONCATENATE,
461
            'AssignOp_Div'        => Operators\Assignment::DIVISION,
462
            'AssignOp_Minus'      => Operators\Assignment::SUBTRACTION,
463
            'AssignOp_Mod'        => Operators\Assignment::MODULUS,
464
            'AssignOp_Mul'        => Operators\Assignment::MULTIPLICATION,
465
            'AssignOp_Pow'        => Operators\Assignment::POWER,
466
            'AssignOp_Plus'       => Operators\Assignment::ADDITION,
467
            'AssignOp_ShiftLeft'  => Operators\Assignment::SHIFT_LEFT,
468
            'AssignOp_ShiftRight' => Operators\Assignment::SHIFT_RIGHT
469
    ];
470
471
    // </editor-fold>
472
}
473