82bb21d8b42b63989589f291c4c96ee85171677d
[yaffs-website] / vendor / psy / psysh / src / CodeCleaner / ImplicitReturnPass.php
1 <?php
2
3 /*
4  * This file is part of Psy Shell.
5  *
6  * (c) 2012-2018 Justin Hileman
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Psy\CodeCleaner;
13
14 use PhpParser\Node;
15 use PhpParser\Node\Expr;
16 use PhpParser\Node\Expr\Exit_;
17 use PhpParser\Node\Expr\New_;
18 use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
19 use PhpParser\Node\Stmt;
20 use PhpParser\Node\Stmt\Break_;
21 use PhpParser\Node\Stmt\Expression;
22 use PhpParser\Node\Stmt\If_;
23 use PhpParser\Node\Stmt\Namespace_;
24 use PhpParser\Node\Stmt\Return_;
25 use PhpParser\Node\Stmt\Switch_;
26
27 /**
28  * Add an implicit "return" to the last statement, provided it can be returned.
29  */
30 class ImplicitReturnPass extends CodeCleanerPass
31 {
32     /**
33      * @param array $nodes
34      *
35      * @return array
36      */
37     public function beforeTraverse(array $nodes)
38     {
39         return $this->addImplicitReturn($nodes);
40     }
41
42     /**
43      * @param array $nodes
44      *
45      * @return array
46      */
47     private function addImplicitReturn(array $nodes)
48     {
49         // If nodes is empty, it can't have a return value.
50         if (empty($nodes)) {
51             return [new Return_(new New_(new FullyQualifiedName('Psy\CodeCleaner\NoReturnValue')))];
52         }
53
54         $last = end($nodes);
55
56         // Special case a few types of statements to add an implicit return
57         // value (even though they technically don't have any return value)
58         // because showing a return value in these instances is useful and not
59         // very surprising.
60         if ($last instanceof If_) {
61             $last->stmts = $this->addImplicitReturn($last->stmts);
62
63             foreach ($last->elseifs as $elseif) {
64                 $elseif->stmts = $this->addImplicitReturn($elseif->stmts);
65             }
66
67             if ($last->else) {
68                 $last->else->stmts = $this->addImplicitReturn($last->else->stmts);
69             }
70         } elseif ($last instanceof Switch_) {
71             foreach ($last->cases as $case) {
72                 // only add an implicit return to cases which end in break
73                 $caseLast = end($case->stmts);
74                 if ($caseLast instanceof Break_) {
75                     $case->stmts = $this->addImplicitReturn(array_slice($case->stmts, 0, -1));
76                     $case->stmts[] = $caseLast;
77                 }
78             }
79         } elseif ($last instanceof Expr && !($last instanceof Exit_)) {
80             $nodes[count($nodes) - 1] = new Return_($last, [
81                 'startLine' => $last->getLine(),
82                 'endLine'   => $last->getLine(),
83             ]);
84         } elseif ($last instanceof Expression && !($last->expr instanceof Exit_)) {
85             // For PHP Parser 4.x
86             $nodes[count($nodes) - 1] = new Return_($last->expr, [
87                 'startLine' => $last->getLine(),
88                 'endLine'   => $last->getLine(),
89             ]);
90         } elseif ($last instanceof Namespace_) {
91             $last->stmts = $this->addImplicitReturn($last->stmts);
92         }
93
94         // Return a "no return value" for all non-expression statements, so that
95         // PsySH can suppress the `null` that `eval()` returns otherwise.
96         //
97         // Note that statements special cased above (if/elseif/else, switch)
98         // _might_ implicitly return a value before this catch-all return is
99         // reached.
100         //
101         // We're not adding a fallback return after namespace statements,
102         // because code outside namespace statements doesn't really work, and
103         // there's already an implicit return in the namespace statement anyway.
104         if (self::isNonExpressionStmt($last)) {
105             $nodes[] = new Return_(new New_(new FullyQualifiedName('Psy\CodeCleaner\NoReturnValue')));
106         }
107
108         return $nodes;
109     }
110
111     /**
112      * Check whether a given node is a non-expression statement.
113      *
114      * As of PHP Parser 4.x, Expressions are now instances of Stmt as well, so
115      * we'll exclude them here.
116      *
117      * @param Node $node
118      *
119      * @return bool
120      */
121     private static function isNonExpressionStmt(Node $node)
122     {
123         return $node instanceof Stmt &&
124             !$node instanceof Expression &&
125             !$node instanceof Return_ &&
126             !$node instanceof Namespace_;
127     }
128 }