Chris@13
|
1 <?php
|
Chris@13
|
2
|
Chris@13
|
3 /*
|
Chris@13
|
4 * This file is part of Psy Shell.
|
Chris@13
|
5 *
|
Chris@13
|
6 * (c) 2012-2018 Justin Hileman
|
Chris@13
|
7 *
|
Chris@13
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@13
|
9 * file that was distributed with this source code.
|
Chris@13
|
10 */
|
Chris@13
|
11
|
Chris@13
|
12 namespace Psy\CodeCleaner;
|
Chris@13
|
13
|
Chris@13
|
14 use PhpParser\Node;
|
Chris@13
|
15 use PhpParser\Node\Name;
|
Chris@13
|
16 use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
|
Chris@13
|
17 use PhpParser\Node\Stmt\GroupUse;
|
Chris@13
|
18 use PhpParser\Node\Stmt\Namespace_;
|
Chris@13
|
19 use PhpParser\Node\Stmt\Use_;
|
Chris@13
|
20 use PhpParser\NodeTraverser;
|
Chris@13
|
21
|
Chris@13
|
22 /**
|
Chris@13
|
23 * Provide implicit use statements for subsequent execution.
|
Chris@13
|
24 *
|
Chris@13
|
25 * The use statement pass remembers the last use statement line encountered:
|
Chris@13
|
26 *
|
Chris@13
|
27 * use Foo\Bar as Baz;
|
Chris@13
|
28 *
|
Chris@13
|
29 * ... which it then applies implicitly to all future evaluated code, until the
|
Chris@13
|
30 * current namespace is replaced by another namespace.
|
Chris@13
|
31 */
|
Chris@13
|
32 class UseStatementPass extends CodeCleanerPass
|
Chris@13
|
33 {
|
Chris@13
|
34 private $aliases = [];
|
Chris@13
|
35 private $lastAliases = [];
|
Chris@13
|
36 private $lastNamespace = null;
|
Chris@13
|
37
|
Chris@13
|
38 /**
|
Chris@13
|
39 * Re-load the last set of use statements on re-entering a namespace.
|
Chris@13
|
40 *
|
Chris@13
|
41 * This isn't how namespaces normally work, but because PsySH has to spin
|
Chris@13
|
42 * up a new namespace for every line of code, we do this to make things
|
Chris@13
|
43 * work like you'd expect.
|
Chris@13
|
44 *
|
Chris@13
|
45 * @param Node $node
|
Chris@13
|
46 */
|
Chris@13
|
47 public function enterNode(Node $node)
|
Chris@13
|
48 {
|
Chris@13
|
49 if ($node instanceof Namespace_) {
|
Chris@13
|
50 // If this is the same namespace as last namespace, let's do ourselves
|
Chris@13
|
51 // a favor and reload all the aliases...
|
Chris@17
|
52 if (\strtolower($node->name) === \strtolower($this->lastNamespace)) {
|
Chris@13
|
53 $this->aliases = $this->lastAliases;
|
Chris@13
|
54 }
|
Chris@13
|
55 }
|
Chris@13
|
56 }
|
Chris@13
|
57
|
Chris@13
|
58 /**
|
Chris@13
|
59 * If this statement is a namespace, forget all the aliases we had.
|
Chris@13
|
60 *
|
Chris@13
|
61 * If it's a use statement, remember the alias for later. Otherwise, apply
|
Chris@13
|
62 * remembered aliases to the code.
|
Chris@13
|
63 *
|
Chris@13
|
64 * @param Node $node
|
Chris@13
|
65 */
|
Chris@13
|
66 public function leaveNode(Node $node)
|
Chris@13
|
67 {
|
Chris@13
|
68 if ($node instanceof Use_) {
|
Chris@13
|
69 // Store a reference to every "use" statement, because we'll need
|
Chris@13
|
70 // them in a bit.
|
Chris@13
|
71 foreach ($node->uses as $use) {
|
Chris@17
|
72 $alias = $use->alias ?: \end($use->name->parts);
|
Chris@17
|
73 $this->aliases[\strtolower($alias)] = $use->name;
|
Chris@13
|
74 }
|
Chris@13
|
75
|
Chris@13
|
76 return NodeTraverser::REMOVE_NODE;
|
Chris@13
|
77 } elseif ($node instanceof GroupUse) {
|
Chris@13
|
78 // Expand every "use" statement in the group into a full, standalone
|
Chris@13
|
79 // "use" and store 'em with the others.
|
Chris@13
|
80 foreach ($node->uses as $use) {
|
Chris@17
|
81 $alias = $use->alias ?: \end($use->name->parts);
|
Chris@17
|
82 $this->aliases[\strtolower($alias)] = Name::concat($node->prefix, $use->name, [
|
Chris@13
|
83 'startLine' => $node->prefix->getAttribute('startLine'),
|
Chris@13
|
84 'endLine' => $use->name->getAttribute('endLine'),
|
Chris@13
|
85 ]);
|
Chris@13
|
86 }
|
Chris@13
|
87
|
Chris@13
|
88 return NodeTraverser::REMOVE_NODE;
|
Chris@13
|
89 } elseif ($node instanceof Namespace_) {
|
Chris@13
|
90 // Start fresh, since we're done with this namespace.
|
Chris@13
|
91 $this->lastNamespace = $node->name;
|
Chris@13
|
92 $this->lastAliases = $this->aliases;
|
Chris@13
|
93 $this->aliases = [];
|
Chris@13
|
94 } else {
|
Chris@13
|
95 foreach ($node as $name => $subNode) {
|
Chris@13
|
96 if ($subNode instanceof Name) {
|
Chris@13
|
97 // Implicitly thunk all aliases.
|
Chris@13
|
98 if ($replacement = $this->findAlias($subNode)) {
|
Chris@13
|
99 $node->$name = $replacement;
|
Chris@13
|
100 }
|
Chris@13
|
101 }
|
Chris@13
|
102 }
|
Chris@13
|
103
|
Chris@13
|
104 return $node;
|
Chris@13
|
105 }
|
Chris@13
|
106 }
|
Chris@13
|
107
|
Chris@13
|
108 /**
|
Chris@13
|
109 * Find class/namespace aliases.
|
Chris@13
|
110 *
|
Chris@13
|
111 * @param Name $name
|
Chris@13
|
112 *
|
Chris@13
|
113 * @return FullyQualifiedName|null
|
Chris@13
|
114 */
|
Chris@13
|
115 private function findAlias(Name $name)
|
Chris@13
|
116 {
|
Chris@17
|
117 $that = \strtolower($name);
|
Chris@13
|
118 foreach ($this->aliases as $alias => $prefix) {
|
Chris@13
|
119 if ($that === $alias) {
|
Chris@13
|
120 return new FullyQualifiedName($prefix->toString());
|
Chris@17
|
121 } elseif (\substr($that, 0, \strlen($alias) + 1) === $alias . '\\') {
|
Chris@17
|
122 return new FullyQualifiedName($prefix->toString() . \substr($name, \strlen($alias)));
|
Chris@13
|
123 }
|
Chris@13
|
124 }
|
Chris@13
|
125 }
|
Chris@13
|
126 }
|