Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of Psy Shell.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) 2012-2017 Justin Hileman
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Psy\CodeCleaner;
|
Chris@0
|
13
|
Chris@0
|
14 use PhpParser\Node;
|
Chris@0
|
15 use PhpParser\Node\Scalar\DNumber;
|
Chris@0
|
16 use PhpParser\Node\Scalar\LNumber;
|
Chris@0
|
17 use PhpParser\Node\Stmt\Break_;
|
Chris@0
|
18 use PhpParser\Node\Stmt\Continue_;
|
Chris@0
|
19 use PhpParser\Node\Stmt\Do_;
|
Chris@0
|
20 use PhpParser\Node\Stmt\For_;
|
Chris@0
|
21 use PhpParser\Node\Stmt\Foreach_;
|
Chris@0
|
22 use PhpParser\Node\Stmt\Switch_;
|
Chris@0
|
23 use PhpParser\Node\Stmt\While_;
|
Chris@0
|
24 use Psy\Exception\FatalErrorException;
|
Chris@0
|
25
|
Chris@0
|
26 /**
|
Chris@0
|
27 * The loop context pass handles invalid `break` and `continue` statements.
|
Chris@0
|
28 */
|
Chris@0
|
29 class LoopContextPass extends CodeCleanerPass
|
Chris@0
|
30 {
|
Chris@0
|
31 private $isPHP54;
|
Chris@0
|
32 private $loopDepth;
|
Chris@0
|
33
|
Chris@0
|
34 public function __construct()
|
Chris@0
|
35 {
|
Chris@0
|
36 $this->isPHP54 = version_compare(PHP_VERSION, '5.4.0', '>=');
|
Chris@0
|
37 }
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@0
|
40 * {@inheritdoc}
|
Chris@0
|
41 */
|
Chris@0
|
42 public function beforeTraverse(array $nodes)
|
Chris@0
|
43 {
|
Chris@0
|
44 $this->loopDepth = 0;
|
Chris@0
|
45 }
|
Chris@0
|
46
|
Chris@0
|
47 /**
|
Chris@0
|
48 * @throws FatalErrorException if the node is a break or continue in a non-loop or switch context
|
Chris@0
|
49 * @throws FatalErrorException if the node is trying to break out of more nested structures than exist
|
Chris@0
|
50 * @throws FatalErrorException if the node is a break or continue and has a non-numeric argument
|
Chris@0
|
51 * @throws FatalErrorException if the node is a break or continue and has an argument less than 1
|
Chris@0
|
52 *
|
Chris@0
|
53 * @param Node $node
|
Chris@0
|
54 */
|
Chris@0
|
55 public function enterNode(Node $node)
|
Chris@0
|
56 {
|
Chris@0
|
57 switch (true) {
|
Chris@0
|
58 case $node instanceof Do_:
|
Chris@0
|
59 case $node instanceof For_:
|
Chris@0
|
60 case $node instanceof Foreach_:
|
Chris@0
|
61 case $node instanceof Switch_:
|
Chris@0
|
62 case $node instanceof While_:
|
Chris@0
|
63 $this->loopDepth++;
|
Chris@0
|
64 break;
|
Chris@0
|
65
|
Chris@0
|
66 case $node instanceof Break_:
|
Chris@0
|
67 case $node instanceof Continue_:
|
Chris@0
|
68 $operator = $node instanceof Break_ ? 'break' : 'continue';
|
Chris@0
|
69
|
Chris@0
|
70 if ($this->loopDepth === 0) {
|
Chris@0
|
71 $msg = sprintf("'%s' not in the 'loop' or 'switch' context", $operator);
|
Chris@0
|
72 throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 if ($node->num instanceof LNumber || $node->num instanceof DNumber) {
|
Chris@0
|
76 $num = $node->num->value;
|
Chris@0
|
77 if ($this->isPHP54 && ($node->num instanceof DNumber || $num < 1)) {
|
Chris@0
|
78 $msg = sprintf("'%s' operator accepts only positive numbers", $operator);
|
Chris@0
|
79 throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
|
Chris@0
|
80 }
|
Chris@0
|
81
|
Chris@0
|
82 if ($num > $this->loopDepth) {
|
Chris@0
|
83 $msg = sprintf("Cannot '%s' %d levels", $operator, $num);
|
Chris@0
|
84 throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
|
Chris@0
|
85 }
|
Chris@0
|
86 } elseif ($node->num && $this->isPHP54) {
|
Chris@0
|
87 $msg = sprintf("'%s' operator with non-constant operand is no longer supported", $operator);
|
Chris@0
|
88 throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
|
Chris@0
|
89 }
|
Chris@0
|
90 break;
|
Chris@0
|
91 }
|
Chris@0
|
92 }
|
Chris@0
|
93
|
Chris@0
|
94 /**
|
Chris@0
|
95 * @param Node $node
|
Chris@0
|
96 */
|
Chris@0
|
97 public function leaveNode(Node $node)
|
Chris@0
|
98 {
|
Chris@0
|
99 switch (true) {
|
Chris@0
|
100 case $node instanceof Do_:
|
Chris@0
|
101 case $node instanceof For_:
|
Chris@0
|
102 case $node instanceof Foreach_:
|
Chris@0
|
103 case $node instanceof Switch_:
|
Chris@0
|
104 case $node instanceof While_:
|
Chris@0
|
105 $this->loopDepth--;
|
Chris@0
|
106 break;
|
Chris@0
|
107 }
|
Chris@0
|
108 }
|
Chris@0
|
109 }
|