Completed
Pull Request — master (#293)
by Дмитрий
02:40
created

ControlFlowGraph::passNodes()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 31
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 27
nc 9
nop 2
dl 0
loc 31
rs 4.909
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\ControlFlow;
7
8
use PhpParser\Node\Stmt\Function_;
9
use PHPSA\ControlFlow\Node\Assign;
10
use PHPSA\ControlFlow\Node\Exit_;
11
use PHPSA\ControlFlow\Node\JumpIf;
12
use PHPSA\ControlFlow\Node\Return_;
13
use PHPSA\ControlFlow\Node\Throw_;
14
15
class ControlFlowGraph
16
{
17
    protected $lastBlockId = 1;
18
19
    /**
20
     * @var Block
21
     */
22
    protected $root;
23
24
    public function __construct($statement)
25
    {
26
        $this->root = new Block($this->lastBlockId++);
27
28
        if ($statement instanceof Function_) {
29
            if ($statement->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $statement->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
30
                $this->passNodes($statement->stmts, $this->root);
31
            }
32
        }
33
    }
34
35
    protected function passNodes(array $nodes, Block $block)
36
    {
37
        foreach ($nodes as $stmt) {
38
            switch (get_class($stmt)) {
39
                case \PhpParser\Node\Expr\Assign::class:
40
                    $this->passAssign($stmt, $block);
41
                    break;
42
                case \PhpParser\Node\Stmt\Return_::class:
43
                    $this->passReturn($stmt, $block);
44
                    break;
45
                case \PhpParser\Node\Stmt\For_::class:
46
                    $block = $this->passFor($stmt, $block);
47
                    break;
48
                case \PhpParser\Node\Stmt\If_::class:
49
                    $block = $this->passIf($stmt, $block);
50
                    break;
51
                case \PhpParser\Node\Stmt\While_::class:
52
                    $block = $this->passWhile($stmt, $block);
53
                    break;
54
                case \PhpParser\Node\Stmt\Throw_::class:
55
                    $this->passThrow($stmt, $block);
56
                    break;
57
                case \PhpParser\Node\Expr\Exit_::class:
58
                    $block->addChildren(new Exit_());
59
                    break;
60
                default:
61
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
62
                    break;
63
            }
64
        }
65
    }
66
67
    protected function passIf(\PhpParser\Node\Stmt\If_ $if, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $if. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
68
    {
69
        $jumpIf = new JumpIf($this->lastBlockId++);
0 ignored issues
show
Unused Code introduced by
The call to JumpIf::__construct() has too many arguments starting with $this->lastBlockId++.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
70
71
        $trueBlock = new Block($this->lastBlockId++);
72
        $this->passNodes($if->stmts, $trueBlock);
73
74
        $jumpIf->setIf($trueBlock);
75
76
        $elseBlock = null;
77
78
        if ($if->else) {
79
            if ($if->else->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $if->else->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
80
                $elseBlock = new Block($this->lastBlockId++);
81
                $this->passNodes($if->else->stmts, $elseBlock);
82
83
                $jumpIf->setElse($elseBlock);
84
            }
85
        }
86
87
        $block->addChildren(
88
            $jumpIf
89
        );
90
91
        $exitBlock = new Block($this->lastBlockId++);
92
        $trueBlock->setExit($exitBlock);
93
94
        if ($elseBlock) {
95
            $elseBlock->setExit($exitBlock);
96
        }
97
98
        return $exitBlock;
99
    }
100
101
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
102
    {
103
        $this->passNodes($for->init, $block);
104
105
        $block->setExit(
106
            $loop = new Block($this->lastBlockId++)
107
        );
108
        $this->passNodes($for->stmts, $loop);
109
110
        $loop->setExit(
111
            $after = new Block($this->lastBlockId++)
112
        );
113
        return $after;
114
    }
115
116
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
117
    {
118
        $block->setExit(
119
            $loop = new Block($this->lastBlockId++)
120
        );
121
        $this->passNodes($while->stmts, $loop);
122
123
        $loop->setExit(
124
            $after = new Block($this->lastBlockId++)
125
        );
126
        return $after;
127
    }
128
129
    protected function passThrow(\PhpParser\Node\Stmt\Throw_ $throw_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $throw_ is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
130
    {
131
        $block->addChildren(new Throw_());
132
    }
133
134
    protected function passAssign(\PhpParser\Node\Expr\Assign $assign, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $assign is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
135
    {
136
        $block->addChildren(new Assign());
137
    }
138
139
    protected function passReturn(\PhpParser\Node\Stmt\Return_ $return_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $return_ is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
140
    {
141
        $block->addChildren(new Return_());
142
    }
143
144
    /**
145
     * @return Block
146
     */
147
    public function getRoot()
148
    {
149
        return $this->root;
150
    }
151
}
152