diff --git a/src/Context.php b/src/Context.php index 75d3edd..ea856d9 100644 --- a/src/Context.php +++ b/src/Context.php @@ -2,9 +2,22 @@ namespace Phabel; +use SplStack; + /** * @author Daniil Gentili + * @license MIT */ class Context { + /** + * Parent nodes stack + * + * @var SplStack + */ + public SplStack $parents; + public function __construct() + { + $this->parents = new SplStack; + } } diff --git a/src/RootNode.php b/src/RootNode.php new file mode 100644 index 0000000..fed6bc7 --- /dev/null +++ b/src/RootNode.php @@ -0,0 +1,32 @@ +stmts = $stmts; + parent::__construct($attributes); + } + public function getSubNodeNames(): array + { + return ['stmts']; + } + public function getType(): string + { + return 'rootNode'; + } +} \ No newline at end of file diff --git a/src/Target/Php70/AnonymousClassReplacer.php b/src/Target/Php70/AnonymousClassReplacer.php index 8fa6da4..9d2bf75 100644 --- a/src/Target/Php70/AnonymousClassReplacer.php +++ b/src/Target/Php70/AnonymousClassReplacer.php @@ -2,44 +2,71 @@ namespace Phabel\Target\Php70; +use Phabel\Context; use Phabel\Plugin; +use Phabel\RootNode; use PhpParser\Node; +use PhpParser\Node\Expr\New_; +use PhpParser\Node\Stmt\Namespace_; class AnonymousClassReplacer extends Plugin { /** - * @var array + * Anonymous class count. */ - protected $anonymousClassNodes = []; - public static $count = 0; + private int $count = 0; /** - * {@inheritdoc} + * Current file name hash. */ - public function leaveNode(Node $node) - { - if (!$node instanceof Node\Expr\New_) { - return; - } + private string $fileName = ''; + /** + * {@inheritDoc} + */ + public function shouldRunFile(string $file): bool + { + $this->fileName = \hash('sha256', $file); + return parent::shouldRunFile($file); + } + + /** + * Leave new. + * + * @param New_ $node New stmt + * @param Context $ctx Context + * + * @return void + */ + public function leaveNew(New_ $node, Context $ctx): void + { $classNode = $node->class; if (!$classNode instanceof Node\Stmt\Class_) { return; } - $newClassName = 'AnonymousClass'.(self::$count++); + $classNode->name = 'PhabelAnonymousClass'.$this->fileName.($this->count++); - $classNode->name = $newClassName; - - $this->anonymousClassNodes[] = $classNode; - - // Generate new code that instantiate our new class - $newNode = new Node\Expr\New_( - new Node\Expr\ConstFetch( - new Node\Name($newClassName) - ), - $node->args + $node->class = new Node\Expr\ConstFetch( + new Node\Name($classNode->name) ); - return $newNode; + $prevNode = $node; + foreach ($ctx->parents as $node) { + if ($node instanceof Namespace_ || $node instanceof RootNode) { + $foundIndex = -1; + foreach ($node->stmts as $index => $curNode) { + if ($curNode === $prevNode) { + $foundIndex = $index; + break; + } + } + if ($foundIndex >= 0) { + \array_splice($node->stmts, $foundIndex, 0, [$classNode]); + return; + } + } + $prevNode = $node; + } + throw new \RuntimeException('Could not find hook for inserting anonymous class!'); } } diff --git a/src/Traverser.php b/src/Traverser.php index ff0ad34..897fa91 100644 --- a/src/Traverser.php +++ b/src/Traverser.php @@ -94,23 +94,10 @@ class Traverser } elseif (!$reducedQueue->count()) { return; } - $ast = $this->parser->parse(\file_get_contents($file)); + $ast = new RootNode($this->parser->parse(\file_get_contents($file))); + $context = new Context; foreach ($reducedQueue as $queue) { - $this->traverseArray($ast, $queue); - } - } - /** - * Traverse array of nodes. - * - * @param Node[] $nodes Nodes - * @param SplQueue $plugins Plugins - * - * @return void - */ - public function traverseArray(array &$nodes, SplQueue $plugins): void - { - foreach ($nodes as &$node) { - $this->traverseNode($node, $plugins); + $this->traverseNode($ast, $queue, $context); } } /** @@ -118,9 +105,11 @@ class Traverser * * @param Node &$node Node * @param SplQueue $plugins Plugins + * @param Context $context Context + * * @return void */ - public function traverseNode(Node &$node, SplQueue $plugins): void + public function traverseNode(Node &$node, SplQueue $plugins, Context $context): void { foreach ($plugins as $plugin) { foreach (PluginCache::enterMethods(\get_class($plugin)) as $type => $methods) { @@ -128,7 +117,7 @@ class Traverser continue; } foreach ($methods as $method) { - $result = $plugin->{$method}($node); + $result = $plugin->{$method}($node, $context); if ($result instanceof Node) { if (!$result instanceof $node) { $node = $result; @@ -139,21 +128,25 @@ class Traverser } } } + $context->parents->push($node); foreach ($node->getSubNodeNames() as $name) { $subNode = &$node->{$name}; if (\is_array($subNode)) { - $this->traverseArray($subNode, $plugins); + foreach ($subNode as &$subNodeNode) { + $this->traverseNode($subNodeNode, $plugins, $context); + } } else { - $this->traverseNode($subNode, $plugins); + $this->traverseNode($subNode, $plugins, $context); } } + $context->parents->pop(); foreach ($plugins as $plugin) { foreach (PluginCache::leaveMethods(\get_class($plugin)) as $type => $methods) { if (!$node instanceof $type) { continue; } foreach ($methods as $method) { - $result = $plugin->{$method}($node); + $result = $plugin->{$method}($node, $context); if ($result instanceof Node) { if (!$result instanceof $node) { $node = $result;