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

ControlFlowGraph::passNode()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 42
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 30

Importance

Changes 0
Metric Value
cc 12
eloc 38
nc 12
nop 2
dl 0
loc 42
ccs 20
cts 40
cp 0.5
crap 30
rs 5.1612
c 0
b 0
f 0

How to fix   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
 * @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;
10
11
class ControlFlowGraph
12
{
13
    protected $lastBlockId = 1;
14
15
    /**
16
     * @var Block
17
     */
18
    protected $root;
19
20 5
    public function __construct($statement)
21
    {
22 5
        $this->root = new Block($this->lastBlockId++);
23
24 5
        if ($statement instanceof Function_) {
25 5
            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...
26 4
                $this->passNodes($statement->stmts, $this->root);
27 4
            }
28 5
        }
29 5
    }
30
31 4
    protected function passNodes(array $nodes, Block $block)
32
    {
33 4
        foreach ($nodes as $stmt) {
34 4
            $this->passNode($stmt, $block);
35 4
        }
36 4
    }
37
38 4
    protected function passNode($stmt, Block $block)
39
    {
40 4
        switch (get_class($stmt)) {
41 4
            case \PhpParser\Node\Expr\Assign::class:
42
                $this->passAssign($stmt, $block);
43
                break;
44 4
            case \PhpParser\Node\Stmt\Return_::class:
45 3
                $this->passReturn($stmt, $block);
46 3
                break;
47 1
            case \PhpParser\Node\Stmt\For_::class:
48
                $block = $this->passFor($stmt, $block);
0 ignored issues
show
Unused Code introduced by
$block is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
49
                break;
50 1
            case \PhpParser\Node\Stmt\If_::class:
51
                $block = $this->passIf($stmt, $block);
0 ignored issues
show
Unused Code introduced by
$block is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
52
                break;
53 1
            case \PhpParser\Node\Stmt\While_::class:
54
                $block = $this->passWhile($stmt, $block);
0 ignored issues
show
Unused Code introduced by
$block is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
55
                break;
56 1
            case \PhpParser\Node\Stmt\Do_::class:
57
                $block = $this->passDo($stmt, $block);
0 ignored issues
show
Unused Code introduced by
$block is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
58
                break;
59 1
            case \PhpParser\Node\Stmt\Throw_::class:
60
                $this->passThrow($stmt, $block);
61
                break;
62 1
            case \PhpParser\Node\Expr\Exit_::class:
63
                $block->addChildren(new Node\ExitNode());
64
                break;
65 1
            case \PhpParser\Node\Stmt\Label::class:
66
                $block = $this->createNewBlockIfNeeded($block);
67
                $block->label = $stmt->name;
68
                break;
69 1
            case \PhpParser\Node\Stmt\TryCatch::class:
70
                $block = $this->passTryCatch($stmt, $block);
0 ignored issues
show
Unused Code introduced by
$block is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
71
                break;
72 1
            case \PhpParser\Node\Stmt\Nop::class:
73
                // ignore commented code
74
                break;
75 1
            default:
76 1
                echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
77 1
                break;
78 4
        }
79 4
    }
80
81
    /**
82
     * If current block is not empty, lets create a new one
83
     *
84
     * @param Block $block
85
     * @return Block
86
     */
87
    protected function createNewBlockIfNeeded(Block $block)
88
    {
89
        if ($block->getChildrens()) {
90
            $block->setExit(
91
                $block = new Block($this->lastBlockId++)
92
            );
93
        }
94
95
        return $block;
96
    }
97
98
    protected function passExpr(\PhpParser\Node\Expr $expr)
99
    {
100
        switch (get_class($expr)) {
101
            case \PhpParser\Node\Expr\BinaryOp\Greater::class:
102
                return new Node\Expr\Greater();
103
            case \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class:
104
                return new Node\Expr\GreaterOrEqual();
105
            case \PhpParser\Node\Expr\Instanceof_::class:
106
                return new Node\Expr\InstanceOfExpr();
107
            default:
108
                echo 'Unimplemented ' . get_class($expr) . PHP_EOL;
109
        }
110
111
        return new Node\UnknownNode();
112
    }
113
114
    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...
115
    {
116
        $trueBlock = new Block($this->lastBlockId++);
117
        $this->passNodes($if->stmts, $trueBlock);
118
119
        $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock);
120
121
        $elseBlock = null;
122
123
        if ($if->else) {
124
            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...
125
                $elseBlock = new Block($this->lastBlockId++);
126
                $this->passNodes($if->else->stmts, $elseBlock);
127
128
                $jumpIf->setElse($elseBlock);
129
            }
130
        }
131
132
        $block->addChildren(
133
            $jumpIf
134
        );
135
136
        $exitBlock = new Block($this->lastBlockId++);
137
        $trueBlock->setExit($exitBlock);
138
139
        if ($elseBlock) {
140
            $elseBlock->setExit($exitBlock);
141
        }
142
143
        return $exitBlock;
144
    }
145
146
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
147
    {
148
        $this->passNodes($for->init, $block);
149
150
        $block->setExit(
151
            $loop = new Block($this->lastBlockId++)
152
        );
153
        $this->passNodes($for->stmts, $loop);
154
155
        $loop->setExit(
156
            $after = new Block($this->lastBlockId++)
157
        );
158
        return $after;
159
    }
160
161
    protected function passDo(\PhpParser\Node\Stmt\Do_ $do, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $do. 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...
162
    {
163
        $loop = new Block($this->lastBlockId++);
164
        $this->passNodes($do->stmts, $loop);
165
166
        $block->setExit($loop);
167
168
        $cond = new Block($this->lastBlockId++);
169
        $loop->setExit($cond);
170
171
        $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop);
172
        $cond->addChildren($jumpIf);
173
174
        $exitBlock = new Block($this->lastBlockId++);
175
        $jumpIf->setElse($exitBlock);
176
177
        return $exitBlock;
178
    }
179
180
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
181
    {
182
        $cond = new Block($this->lastBlockId++);
183
        $block->setExit(
184
            $cond
185
        );
186
187
        $loop = new Block($this->lastBlockId++);
188
189
        $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop);
190
        $cond->addChildren($jumpIf);
191
192
        $this->passNodes($while->stmts, $loop);
193
194
        $loop->addChildren(new Node\JumpNode($cond));
195
        //$loop->setExit($cond);
196
197
        $after = new Block($this->lastBlockId++);
198
        $jumpIf->setElse($after);
199
200
        return $after;
201
    }
202
203
    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...
204
    {
205
        $block->addChildren(new Node\ThrowNode());
206
    }
207
208
    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...
209
    {
210
        $block->addChildren(new Node\AssignNode());
211
    }
212
213 3
    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...
214
    {
215 3
        $block->addChildren(new Node\ReturnNode());
216 3
    }
217
218
    protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block)
219
    {
220
        $try = new Block($this->lastBlockId++);
221
        $this->passNodes($stmt->stmts, $try);
222
223
        $block->setExit($try);
224
225
        if ($stmt->finally) {
226
            $finally = new Block($this->lastBlockId++);
227
            $this->passNodes($stmt->finally->stmts, $finally);
228
229
            $try->setExit($finally);
230
            return $finally;
231
        }
232
233
        return $try;
234
    }
235
236
    /**
237
     * @return Block
238
     */
239
    public function getRoot()
240
    {
241
        return $this->root;
242
    }
243
}
244