Chris@16
|
1 <?php
|
Chris@16
|
2
|
Chris@16
|
3 /*
|
Chris@16
|
4 * This file is part of Psy Shell.
|
Chris@16
|
5 *
|
Chris@16
|
6 * (c) 2012-2018 Justin Hileman
|
Chris@16
|
7 *
|
Chris@16
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@16
|
9 * file that was distributed with this source code.
|
Chris@16
|
10 */
|
Chris@16
|
11
|
Chris@16
|
12 namespace Psy\Command\TimeitCommand;
|
Chris@16
|
13
|
Chris@16
|
14 use PhpParser\Node;
|
Chris@16
|
15 use PhpParser\Node\Arg;
|
Chris@16
|
16 use PhpParser\Node\Expr;
|
Chris@16
|
17 use PhpParser\Node\Expr\StaticCall;
|
Chris@16
|
18 use PhpParser\Node\FunctionLike;
|
Chris@16
|
19 use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
|
Chris@16
|
20 use PhpParser\Node\Stmt\Expression;
|
Chris@16
|
21 use PhpParser\Node\Stmt\Return_;
|
Chris@16
|
22 use PhpParser\NodeVisitorAbstract;
|
Chris@16
|
23 use Psy\CodeCleaner\NoReturnValue;
|
Chris@16
|
24
|
Chris@16
|
25 /**
|
Chris@16
|
26 * A node visitor for instrumenting code to be executed by the `timeit` command.
|
Chris@16
|
27 *
|
Chris@16
|
28 * Injects `TimeitCommand::markStart()` at the start of code to be executed, and
|
Chris@16
|
29 * `TimeitCommand::markEnd()` at the end, and on top-level return statements.
|
Chris@16
|
30 */
|
Chris@16
|
31 class TimeitVisitor extends NodeVisitorAbstract
|
Chris@16
|
32 {
|
Chris@16
|
33 private $functionDepth;
|
Chris@16
|
34
|
Chris@16
|
35 /**
|
Chris@16
|
36 * {@inheritdoc}
|
Chris@16
|
37 */
|
Chris@16
|
38 public function beforeTraverse(array $nodes)
|
Chris@16
|
39 {
|
Chris@16
|
40 $this->functionDepth = 0;
|
Chris@16
|
41 }
|
Chris@16
|
42
|
Chris@16
|
43 /**
|
Chris@16
|
44 * {@inheritdoc}
|
Chris@16
|
45 */
|
Chris@16
|
46 public function enterNode(Node $node)
|
Chris@16
|
47 {
|
Chris@16
|
48 // keep track of nested function-like nodes, because they can have
|
Chris@16
|
49 // returns statements... and we don't want to call markEnd for those.
|
Chris@16
|
50 if ($node instanceof FunctionLike) {
|
Chris@16
|
51 $this->functionDepth++;
|
Chris@16
|
52
|
Chris@16
|
53 return;
|
Chris@16
|
54 }
|
Chris@16
|
55
|
Chris@16
|
56 // replace any top-level `return` statements with a `markEnd` call
|
Chris@16
|
57 if ($this->functionDepth === 0 && $node instanceof Return_) {
|
Chris@16
|
58 return new Return_($this->getEndCall($node->expr), $node->getAttributes());
|
Chris@16
|
59 }
|
Chris@16
|
60 }
|
Chris@16
|
61
|
Chris@16
|
62 /**
|
Chris@16
|
63 * {@inheritdoc}
|
Chris@16
|
64 */
|
Chris@16
|
65 public function leaveNode(Node $node)
|
Chris@16
|
66 {
|
Chris@16
|
67 if ($node instanceof FunctionLike) {
|
Chris@16
|
68 $this->functionDepth--;
|
Chris@16
|
69 }
|
Chris@16
|
70 }
|
Chris@16
|
71
|
Chris@16
|
72 /**
|
Chris@16
|
73 * {@inheritdoc}
|
Chris@16
|
74 */
|
Chris@16
|
75 public function afterTraverse(array $nodes)
|
Chris@16
|
76 {
|
Chris@16
|
77 // prepend a `markStart` call
|
Chris@17
|
78 \array_unshift($nodes, $this->maybeExpression($this->getStartCall()));
|
Chris@16
|
79
|
Chris@16
|
80 // append a `markEnd` call (wrapping the final node, if it's an expression)
|
Chris@17
|
81 $last = $nodes[\count($nodes) - 1];
|
Chris@16
|
82 if ($last instanceof Expr) {
|
Chris@17
|
83 \array_pop($nodes);
|
Chris@16
|
84 $nodes[] = $this->getEndCall($last);
|
Chris@16
|
85 } elseif ($last instanceof Expression) {
|
Chris@17
|
86 \array_pop($nodes);
|
Chris@16
|
87 $nodes[] = new Expression($this->getEndCall($last->expr), $last->getAttributes());
|
Chris@16
|
88 } elseif ($last instanceof Return_) {
|
Chris@16
|
89 // nothing to do here, we're already ending with a return call
|
Chris@16
|
90 } else {
|
Chris@16
|
91 $nodes[] = $this->maybeExpression($this->getEndCall());
|
Chris@16
|
92 }
|
Chris@16
|
93
|
Chris@16
|
94 return $nodes;
|
Chris@16
|
95 }
|
Chris@16
|
96
|
Chris@16
|
97 /**
|
Chris@16
|
98 * Get PhpParser AST nodes for a `markStart` call.
|
Chris@16
|
99 *
|
Chris@16
|
100 * @return PhpParser\Node\Expr\StaticCall
|
Chris@16
|
101 */
|
Chris@16
|
102 private function getStartCall()
|
Chris@16
|
103 {
|
Chris@16
|
104 return new StaticCall(new FullyQualifiedName('Psy\Command\TimeitCommand'), 'markStart');
|
Chris@16
|
105 }
|
Chris@16
|
106
|
Chris@16
|
107 /**
|
Chris@16
|
108 * Get PhpParser AST nodes for a `markEnd` call.
|
Chris@16
|
109 *
|
Chris@16
|
110 * Optionally pass in a return value.
|
Chris@16
|
111 *
|
Chris@16
|
112 * @param Expr|null $arg
|
Chris@16
|
113 *
|
Chris@16
|
114 * @return PhpParser\Node\Expr\StaticCall
|
Chris@16
|
115 */
|
Chris@16
|
116 private function getEndCall(Expr $arg = null)
|
Chris@16
|
117 {
|
Chris@16
|
118 if ($arg === null) {
|
Chris@16
|
119 $arg = NoReturnValue::create();
|
Chris@16
|
120 }
|
Chris@16
|
121
|
Chris@16
|
122 return new StaticCall(new FullyQualifiedName('Psy\Command\TimeitCommand'), 'markEnd', [new Arg($arg)]);
|
Chris@16
|
123 }
|
Chris@16
|
124
|
Chris@16
|
125 /**
|
Chris@16
|
126 * Compatibility shim for PHP Parser 3.x.
|
Chris@16
|
127 *
|
Chris@16
|
128 * Wrap $expr in a PhpParser\Node\Stmt\Expression if the class exists.
|
Chris@16
|
129 *
|
Chris@16
|
130 * @param PhpParser\Node $expr
|
Chris@16
|
131 * @param array $attrs
|
Chris@16
|
132 *
|
Chris@16
|
133 * @return PhpParser\Node\Expr|PhpParser\Node\Stmt\Expression
|
Chris@16
|
134 */
|
Chris@16
|
135 private function maybeExpression($expr, $attrs = [])
|
Chris@16
|
136 {
|
Chris@17
|
137 return \class_exists('PhpParser\Node\Stmt\Expression') ? new Expression($expr, $attrs) : $expr;
|
Chris@16
|
138 }
|
Chris@16
|
139 }
|