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

ControlFlowGraph::passWhile()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 2
dl 0
loc 24
rs 8.9713
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
        $cond = new Block($this->lastBlockId++);
119
        $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...
120
        $cond->addChildren($jumpIf);
121
122
        $block->setExit(
123
            $cond
124
        );
125
126
        $loop = new Block($this->lastBlockId++);
127
        $jumpIf->setIf($loop);
128
129
        $this->passNodes($while->stmts, $loop);
130
131
        $loop->setExit(
132
            $cond
133
        );
134
135
        $after = new Block($this->lastBlockId++);
136
        $jumpIf->setElse($after);
137
138
        return $after;
139
    }
140
141
    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...
142
    {
143
        $block->addChildren(new Throw_());
144
    }
145
146
    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...
147
    {
148
        $block->addChildren(new Assign());
149
    }
150
151
    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...
152
    {
153
        $block->addChildren(new Return_());
154
    }
155
156
    /**
157
     * @return Block
158
     */
159
    public function getRoot()
160
    {
161
        return $this->root;
162
    }
163
}
164