Completed
Pull Request — master (#293)
by Дмитрий
04:25
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
            default:
106
                echo 'Unimplemented ' . get_class($expr) . PHP_EOL;
107
                break;
108
        }
109
    }
110
111
    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...
112
    {
113
        $trueBlock = new Block($this->lastBlockId++);
114
        $this->passNodes($if->stmts, $trueBlock);
115
116
        $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock);
0 ignored issues
show
Bug introduced by
It seems like $this->passExpr($if->cond) targeting PHPSA\ControlFlow\ControlFlowGraph::passExpr() can also be of type null; however, PHPSA\ControlFlow\Node\JumpIfNode::__construct() does only seem to accept object<PHPSA\ControlFlow\Node\AbstractNode>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
117
118
        $elseBlock = null;
119
120
        if ($if->else) {
121
            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...
122
                $elseBlock = new Block($this->lastBlockId++);
123
                $this->passNodes($if->else->stmts, $elseBlock);
124
125
                $jumpIf->setElse($elseBlock);
126
            }
127
        }
128
129
        $block->addChildren(
130
            $jumpIf
131
        );
132
133
        $exitBlock = new Block($this->lastBlockId++);
134
        $trueBlock->setExit($exitBlock);
135
136
        if ($elseBlock) {
137
            $elseBlock->setExit($exitBlock);
138
        }
139
140
        return $exitBlock;
141
    }
142
143
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
144
    {
145
        $this->passNodes($for->init, $block);
146
147
        $block->setExit(
148
            $loop = new Block($this->lastBlockId++)
149
        );
150
        $this->passNodes($for->stmts, $loop);
151
152
        $loop->setExit(
153
            $after = new Block($this->lastBlockId++)
154
        );
155
        return $after;
156
    }
157
158
    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...
159
    {
160
        $loop = new Block($this->lastBlockId++);
161
        $this->passNodes($do->stmts, $loop);
162
163
        $block->setExit($loop);
164
165
        $cond = new Block($this->lastBlockId++);
166
        $loop->setExit($cond);
167
168
        $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop);
0 ignored issues
show
Bug introduced by
It seems like $this->passExpr($do->cond) targeting PHPSA\ControlFlow\ControlFlowGraph::passExpr() can also be of type null; however, PHPSA\ControlFlow\Node\JumpIfNode::__construct() does only seem to accept object<PHPSA\ControlFlow\Node\AbstractNode>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
169
        $cond->addChildren($jumpIf);
170
171
        $exitBlock = new Block($this->lastBlockId++);
172
        $jumpIf->setElse($exitBlock);
173
174
        return $exitBlock;
175
    }
176
177
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
178
    {
179
        $cond = new Block($this->lastBlockId++);
180
        $block->setExit(
181
            $cond
182
        );
183
184
        $loop = new Block($this->lastBlockId++);
185
186
        $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop);
0 ignored issues
show
Bug introduced by
It seems like $this->passExpr($while->cond) targeting PHPSA\ControlFlow\ControlFlowGraph::passExpr() can also be of type null; however, PHPSA\ControlFlow\Node\JumpIfNode::__construct() does only seem to accept object<PHPSA\ControlFlow\Node\AbstractNode>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
187
        $cond->addChildren($jumpIf);
188
189
        $this->passNodes($while->stmts, $loop);
190
191
        $loop->addChildren(new Node\JumpNode($cond));
192
        //$loop->setExit($cond);
193
194
        $after = new Block($this->lastBlockId++);
195
        $jumpIf->setElse($after);
196
197
        return $after;
198
    }
199
200
    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...
201
    {
202
        $block->addChildren(new Node\ThrowNode());
203
    }
204
205
    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...
206
    {
207
        $block->addChildren(new Node\AssignNode());
208
    }
209
210 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...
211
    {
212 3
        $block->addChildren(new Node\ReturnNode());
213 3
    }
214
215
    protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block)
216
    {
217
        $try = new Block($this->lastBlockId++);
218
        $this->passNodes($stmt->stmts, $try);
219
220
        $block->setExit($try);
221
222
        if ($stmt->finally) {
223
            $finally = new Block($this->lastBlockId++);
224
            $this->passNodes($stmt->finally->stmts, $finally);
225
226
            $try->setExit($finally);
227
            return $finally;
228
        }
229
230
        return $try;
231
    }
232
233
    /**
234
     * @return Block
235
     */
236
    public function getRoot()
237
    {
238
        return $this->root;
239
    }
240
}
241