annotate vendor/psy/psysh/src/CodeCleaner/UseStatementPass.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
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 }