Finish graph logic

This commit is contained in:
Daniil Gentili 2020-08-09 13:49:19 +02:00
parent 70710fb26d
commit 0fb5cf8d88
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
28 changed files with 740 additions and 358 deletions

7
.gitignore vendored
View File

@ -1,2 +1,7 @@
/vendor/
.vscode
build
composer.lock
phpunit.xml
vendor
.php_cs.cache
coverage

13
.php_cs.dist Normal file
View File

@ -0,0 +1,13 @@
<?php
$config = new Amp\CodeStyle\Config();
$config->getFinder()
->in(__DIR__ . '/examples')
->in(__DIR__ . '/src')
->in(__DIR__ . '/test');
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
$config->setCacheFile($cacheDir . '/.php_cs.cache');
return $config;

View File

@ -5,11 +5,22 @@
"require": {
"nikic/php-parser": "^4.7"
},
"require-dev": {
"phpunit/phpunit": "^7 | ^8 | ^9",
"amphp/php-cs-fixer-config": "dev-master"
},
"license": "MIT",
"authors": [
{
"name": "Daniil Gentili",
"email": "daniil@daniil.it"
}
]
}
"authors": [{
"name": "Daniil Gentili",
"email": "daniil@daniil.it"
}],
"scripts": {
"check": [
"@cs",
"@test"
],
"cs": "php-cs-fixer fix -v --diff --dry-run",
"cs-fix": "php-cs-fixer fix -v --diff",
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text"
}
}

View File

@ -4,5 +4,4 @@ namespace Phabel;
class Context
{
}
}

View File

@ -6,5 +6,4 @@ use Phabel\Plugin;
class UniqueId extends Plugin
{
}
}

View File

@ -18,6 +18,16 @@ abstract class Plugin implements PluginInterface
* Configuration array.
*/
private array $config = [];
/**
* Set configuration array.
*
* @param array $config
* @return void
*/
public function setConfigArray(array $config)
{
$this->config = $config;
}
/**
* Replace node of one type with another.
*
@ -141,14 +151,28 @@ abstract class Plugin implements PluginInterface
/**
* {@inheritDoc}
*/
public static function needs(): array
public static function runAfter(): array
{
return [];
}
/**
* {@inheritDoc}
*/
public static function extends(): array
public static function runBefore(): array
{
return [];
}
/**
* {@inheritDoc}
*/
public static function runWithBefore(): array
{
return [];
}
/**
* {@inheritDoc}
*/
public static function runWithAfter(): array
{
return [];
}

View File

@ -4,7 +4,6 @@ namespace Spatie\Php7to5\NodeVisitors;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PhpParser\ParserFactory;
class YieldFromReplacer extends NodeVisitorAbstract
{

View File

@ -3,13 +3,7 @@
namespace Spatie\Php7to5\NodeVisitors;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\NodeVisitorAbstract;
use Spatie\Php7to5\Converter;
use Spatie\Php7to5\Exceptions\InvalidPhpCode;
class YieldReturnDetector extends NodeVisitorAbstract
{
@ -26,13 +20,13 @@ class YieldReturnDetector extends NodeVisitorAbstract
if ($node instanceof Node\Expr\Yield_ ||
$node instanceof Node\Expr\YieldFrom
) {
end($this->hasYield)->hasYield = true;
\end($this->hasYield)->hasYield = true;
}
}
public function leaveNode(Node $node)
{
if ($node instanceof Node\FunctionLike) {
array_pop($this->hasYield);
\array_pop($this->hasYield);
}
}
}

View File

@ -24,7 +24,7 @@ class YieldReturnReplacer extends NodeVisitorAbstract
public function leaveNode(Node $node)
{
if ($node instanceof Node\FunctionLike) {
array_pop($this->functions);
\array_pop($this->functions);
return;
}
if (!$node instanceof Node\Stmt\Return_) {
@ -34,7 +34,7 @@ class YieldReturnReplacer extends NodeVisitorAbstract
return new Node\Stmt\Return_();
}
if (!(end($this->functions)->hasYield ?? false)) {
if (!(\end($this->functions)->hasYield ?? false)) {
return;
}

157
src/PluginCache.php Normal file
View File

@ -0,0 +1,157 @@
<?php
namespace Phabel;
/**
* Caches plugin information.
*/
class PluginCache
{
/**
* Enter method names for each plugin.
*
* @var array<class-string<PluginInterface>, string[]>
*/
private static array $enterMethods = [];
/**
* Leave method names.
*
* @var array<class-string<PluginInterface>, string[]>
*/
private static array $leaveMethods = [];
/**
* Cache method information.
*
* @param class-string<PluginInterface> $plugin Plugin
*
* @return void
*/
private static function cacheMethods(string $plugin): void
{
if (!isset(self::$enterMethods[$plugin])) {
self::$enterMethods[$plugin] = [];
self::$leaveMethods[$plugin] = [];
foreach (\get_class_methods($plugin) as $method) {
if (\str_starts_with($method, 'enter')) {
self::$enterMethods[$plugin] []= $method;
} elseif (\str_starts_with($method, 'leave')) {
self::$leaveMethods[$plugin] []= $method;
}
}
}
}
/**
* Return whether this plugin can be required by another plugin.
*
* If false, this plugin should only be extended by other plugins, to reduce complexity.
*
* @param class-string<PluginInterface> $plugin Plugin
*
* @return boolean
*/
public static function canBeRequired(string $plugin): bool
{
self::cacheMethods($plugin);
return empty(self::$leaveMethods[$plugin]);
}
/**
* Get enter methods array.
*
* @param class-string<PluginInterface> $plugin Plugin name
*
* @return string[]
*/
public static function enterMethods(string $plugin): array
{
self::cacheMethods($plugin);
return self::$enterMethods[$plugin];
}
/**
* Get leave methods array.
*
* @param class-string<PluginInterface> $plugin Plugin name
*
* @return string[]
*/
public static function leaveMethods(string $plugin): array
{
self::cacheMethods($plugin);
return self::$leaveMethods[$plugin];
}
/**
* Get runBefore requirements.
*
* @param class-string<PluginInterface> $plugin Plugin
*
* @return array<class-string<PluginInterface>, array>
*/
public static function runBefore(string $plugin): array
{
/** @var array<class-string<PluginInterface>, array<class-string<PluginInterface>, array>> */
static $cache = [];
if (isset($cache[$plugin])) {
return $cache[$plugin];
}
return $cache[$plugin] = self::simplify($plugin::runBefore());
}
/**
* Get runAfter requirements.
*
* @param class-string<PluginInterface> $plugin Plugin
*
* @return array<class-string<PluginInterface>, array>
*/
public static function runAfter(string $plugin): array
{
/** @var array<class-string<PluginInterface>, array<class-string<PluginInterface>, array>> */
static $cache = [];
if (isset($cache[$plugin])) {
return $cache[$plugin];
}
return $cache[$plugin] = self::simplify($plugin::runAfter());
}
/**
* Get runWithBefore requirements.
*
* @param class-string<PluginInterface> $plugin Plugin
*
* @return array<class-string<PluginInterface>, array>
*/
public static function runWithBefore(string $plugin): array
{
/** @var array<class-string<PluginInterface>, array<class-string<PluginInterface>, array>> */
static $cache = [];
if (isset($cache[$plugin])) {
return $cache[$plugin];
}
return $cache[$plugin] = self::simplify($plugin::runWithBefore());
}
/**
* Get runWithAfter requirements.
*
* @param class-string<PluginInterface> $plugin Plugin
*
* @return array<class-string<PluginInterface>, array>
*/
public static function runWithAfter(string $plugin): array
{
/** @var array<class-string<PluginInterface>, array<class-string<PluginInterface>, array>> */
static $cache = [];
if (isset($cache[$plugin])) {
return $cache[$plugin];
}
return $cache[$plugin] = self::simplify($plugin::runWithAfter());
}
/**
* Simplify requirements.
*
* @param (array<class-string<PluginInterface>, array>|class-string<PluginInterface>[]) $requirements Requirements
*
* @return array<class-string<PluginInterface>, array>
*/
private static function simplify(array $requirements): array
{
return isset($requirements[0]) ? \array_fill_keys($requirements, []) : $requirements;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Phabel\PluginGraph;
use Phabel\PluginInterface;
class CircularException extends \Exception
{
/**
* Plugin array.
*
* @var class-string<PluginInterface>[]
*/
private array $plugins = [];
/**
* Constructor.
*
* @param class-string<PluginInterface>[] $plugins Plugin array
* @param \Throwable $previous Previous exception
*/
public function __construct(array $plugins, \Throwable $previous = null)
{
$this->plugins = $plugins;
parent::__construct("Detected circular reference: ".\implode(" => ", $plugins), 0, $previous);
}
/**
* Get plugins.
*
* @return class-string<PluginInterface>[]
*/
public function getPlugins(): array
{
return $this->plugins;
}
}

View File

@ -2,24 +2,22 @@
namespace Phabel\PluginGraph;
use Phabel\PluginInterface;
/**
* Graph API wrapper.
*/
class Graph
{
/**
* Plugin nodes, indexed by plugin name+config.
*
* @var array<class-string<PluginInterface>, array<string, Node>>
* Graph instance.
*/
private array $plugins = [];
private GraphInternal $graph;
/**
* Package contexts.
*
* @var PackageContext[]
* Constructr.
*/
private array $packageContexts = [];
public function __construct()
{
$this->graph = new GraphInternal;
}
/**
* Get new package context.
*
@ -27,9 +25,7 @@ class Graph
*/
public function getPackageContext(): PackageContext
{
$packageContext = new PackageContext;
$this->packageContexts []= $packageContext;
return $packageContext;
return $this->graph->getPackageContext();
}
/**
@ -45,31 +41,15 @@ class Graph
*/
public function addPlugin(string $plugin, array $config, PackageContext $ctx): array
{
$configs = $plugin::splitConfig($config);
$nodes = [];
foreach ($configs as $config) {
$nodes []= $this->addPluginInternal($plugin, $config, $ctx);
}
return $nodes;
return $this->graph->addPlugin($plugin, $config, $ctx);
}
/**
* Add plugin.
* Flatten graph.
*
* @param string $plugin Plugin to add
* @param array $config Plugin configuration
* @param PackageContext $ctx Package context
*
* @psalm-param class-string<PluginInterface> $plugin Plugin name
*
* @return Node
* @return SplQueue<SplQueue<Plugin>>
*/
private function addPluginInternal(string $plugin, array $config, PackageContext $ctx): Node
public function flatten(): \SplQueue
{
$configStr = \var_export($config, true);
if (isset($this->plugins[$plugin][$configStr])) {
return $this->plugins[$plugin][$configStr]->addPackageContext($ctx);
}
$this->plugins[$plugin][$configStr] = $node = new Node;
return $node->init($this, $plugin, $config, $ctx);
return $this->graph->flatten();
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Phabel\PluginGraph;
use Phabel\PluginInterface;
use SplObjectStorage;
use SplQueue;
class GraphInternal
{
/**
* Plugin nodes, indexed by plugin name+config.
*
* @var array<class-string<PluginInterface>, array<string, Node>>
*/
private array $plugins = [];
/**
* Package contexts.
*
* @var PackageContext[]
*/
private array $packageContexts = [];
/**
* Stores list of Nodes that are not required by any other node.
*
* @var SplObjectStorage<Node, void>
*/
private SplObjectStorage $unlinkedNodes;
/**
* Constructor.
*/
public function __construct()
{
$this->unlinkedNodes = new SplObjectStorage;
}
/**
* Get new package context.
*
* @return PackageContext
*/
public function getPackageContext(): PackageContext
{
$packageContext = new PackageContext;
$this->packageContexts []= $packageContext;
return $packageContext;
}
/**
* Add plugin.
*
* @param class-string<PluginInterface> $plugin Plugin to add
* @param array $config Plugin configuration
* @param PackageContext $ctx Package context
*
* @return Node[]
*/
public function addPlugin(string $plugin, array $config, PackageContext $ctx): array
{
if ($config === ['*']) {
if (isset($this->plugins[$plugin])) {
return \array_map(fn (Node $node) => $node->addPackageContext($ctx), $this->plugins[$plugin]);
}
$config = [];
}
$configs = $plugin::splitConfig($config);
$nodes = [];
foreach ($configs as $config) {
$nodes []= $this->addPluginInternal($plugin, $config, $ctx);
}
return $nodes;
}
/**
* Add plugin.
*
* @param string $plugin Plugin to add
* @param array $config Plugin configuration
* @param PackageContext $ctx Package context
*
* @psalm-param class-string<PluginInterface> $plugin Plugin name
*
* @return Node
*/
private function addPluginInternal(string $plugin, array $config, PackageContext $ctx): Node
{
$configStr = \var_export($config, true);
if (isset($this->plugins[$plugin][$configStr])) {
return $this->plugins[$plugin][$configStr]->addPackageContext($ctx);
}
$this->plugins[$plugin][$configStr] = $node = new Node($this);
$this->unlinkedNodes->attach($node);
return $node->init($plugin, $config, $ctx);
}
/**
* Set unlinked node as linked.
*
* @param Node $node
*
* @return void
*/
public function linkNode(Node $node): void
{
$this->unlinkedNodes->detach($node);
}
/**
* Flatten graph.
*
* @return SplQueue<SplQueue<Plugin>>
*/
public function flatten(): \SplQueue
{
if (!$this->plugins) {
return new \SplQueue;
}
if ($this->unlinkedNodes->count()) {
foreach ($this->unlinkedNodes as $node) {
if (isset($initNode)) {
$node = $initNode->merge($node);
}
/** @var Node */
$initNode = $node;
}
return $initNode->circular()->flatten();
}
return \array_values(\array_values($this->plugins)[0])[0]->circular()->flatten();
}
}

View File

@ -2,8 +2,10 @@
namespace Phabel\PluginGraph;
use Phabel\PluginCache;
use Phabel\PluginInterface;
use SplObjectStorage;
use SplQueue;
/**
* Represents a plugin with a certain configuration.
@ -11,66 +13,85 @@ use SplObjectStorage;
class Node
{
/**
* Plugin name.
* Plugins and configs.
*
* @var Plugin
*/
private string $plugin = '';
private Plugin $plugin;
/**
* Plugin configuration.
* Original plugin name.
*
* @var class-string<PluginInterface>
*/
private array $config = [];
private string $name;
/**
* Associated package contexts.
*
* @var SplObjectStorage<PackageContext, void>
*/
private SplObjectStorage $packageContexts;
/**
* Nodes that this node requires.
*
* @var Node[]
* @var SplObjectStorage<Node, void>
*/
private array $requires = [];
private SplObjectStorage $requires;
/**
* Nodes that this node extends.
*
* @var Node[]
* @var SplObjectStorage<Node, void>
*/
private array $extends = [];
private SplObjectStorage $extends;
/**
* Nodes that require this node.
*
* @var Node[]
* @var SplObjectStorage<Node, void>
*/
private array $requiredBy = [];
private SplObjectStorage $requiredBy;
/**
* Nodes that extend this node.
*
* @var Node[]
* @var SplObjectStorage<Node, void>
*/
private array $extendedBy = [];
private SplObjectStorage $extendedBy;
/**
* Whether this node was visited when looking for circular requirement references.
* Graph instance.
*/
private bool $visitedRequires = false;
private GraphInternal $graph;
/**
* Whether this node was visited when looking for circular requirement references.
* Whether this node was visited when looking for circular requirements.
*/
private bool $visitedExtends = false;
private bool $visitedCircular = false;
/**
* Whether this node can be required, or only extended.
*/
private bool $canBeRequired = true;
/**
* Constructor.
*
* @param GraphInternal $graph Graph instance
*/
public function __construct()
public function __construct(GraphInternal $graph)
{
$this->graph = $graph;
$this->packageContexts = new SplObjectStorage;
$this->requiredBy = new SplObjectStorage;
$this->extendedBy = new SplObjectStorage;
$this->requires = new SplObjectStorage;
$this->extends = new SplObjectStorage;
}
/**
* Initialization function.
*
* @param Graph $graph Graph instance
* @param string $plugin Plugin name
* @param array $config Plugin configuration
* @param PackageContext $ctx Context
@ -79,25 +100,31 @@ class Node
*
* @return self
*/
public function init(Graph $graph, string $plugin, array $config, PackageContext $ctx): self
public function init(string $plugin, array $config, PackageContext $ctx): self
{
$this->plugin = $plugin;
$this->config = $config;
$this->name = $plugin;
$this->plugin = new Plugin($plugin, $config);
$this->packageContexts->attach($ctx);
$requirements = self::simplify($plugin::needs());
$extends = self::simplify($plugin::extends());
foreach ($requirements as $class => $config) {
foreach ($graph->addPlugin($class, $config, $ctx) as $node) {
$this->requires []= $node;
$node->requiredBy []= $this;
$this->canBeRequired = PluginCache::canBeRequired($plugin);
foreach (PluginCache::runAfter($plugin) as $class => $config) {
foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) {
$this->require($node);
}
}
foreach ($extends as $class => $config) {
foreach ($graph->addPlugin($class, $config, $ctx) as $node) {
$this->extends []= $node;
$node->extendedBy []= $this;
foreach (PluginCache::runBefore($plugin) as $class => $config) {
foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) {
$node->require($this);
}
}
foreach (PluginCache::runWithAfter($plugin) as $class => $config) {
foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) {
$this->extend($node);
}
}
foreach (PluginCache::runWithBefore($plugin) as $class => $config) {
foreach ($this->graph->addPlugin($class, $config, $ctx) as $node) {
$node->extend($this);
}
}
@ -105,15 +132,43 @@ class Node
}
/**
* Simplify requirements.
* Make node require another node.
*
* @param (array<class-string<PluginInterface>, array>|class-string<PluginInterface>[]) $requirements Requirements
* @param self $node Node
*
* @return array<class-string<PluginInterface>, array>
* @return void
*/
private static function simplify(array $requirements): array
private function require(self $node): void
{
return isset($requirements[0]) ? \array_fill_keys($requirements, []) : $requirements;
if (!$node->canBeRequired) {
$this->extend($node);
return;
}
if ($this->extends->contains($node) || $node->extendedBy->contains($this)) {
$this->extends->detach($node);
$node->extendedBy->detach($this);
}
$this->requires->attach($node);
$node->requiredBy->attach($this);
$this->graph->linkNode($this);
}
/**
* Make node extend another node.
*
* @param self $node Node
*
* @return void
*/
private function extend(self $node): void
{
if ($this->requires->contains($node) || $node->requiredBy->contains($this)) {
return;
}
$this->extends->attach($node);
$node->extendedBy->attach($this);
$this->graph->linkNode($this);
}
/**
@ -139,21 +194,133 @@ class Node
}
/**
* Check if this node requires only one other node.
* Merge node with another node.
*
* @return boolean
* @param self $other Other node
*
* @return Node
*/
private function oneRequire(): bool
public function merge(self $other): Node
{
return \count($this->requires) === 1 && empty($this->extends);
$this->plugin->merge($other->plugin);
foreach ($other->requiredBy as $node) {
$node->require($this);
$node->requires->detach($other);
}
foreach ($other->extendedBy as $node) {
$node->extend($this);
$node->extends->detach($other);
}
return $this;
}
/**
* Flatten tree.
*
* @return SplQueue<SplQueue<PluginInterface>>
*/
public function flatten(): SplQueue
{
/** @var SplQueue<PluginInterface> */
$initQueue = new SplQueue;
/** @var SplQueue<SplQueue<PluginInterface>> */
$queue = new SplQueue;
$queue->enqueue($initQueue);
$this->flattenInternal($queue);
return $queue;
}
/**
* Check if this node extends only one other node.
* Look for circular references.
*
* @return boolean
* @return self
*/
private function oneExtend(): bool
public function circular(): self
{
return \count($this->extends) === 1;
if ($this->visitedCircular) {
$plugins = [$this->name];
foreach (\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, DEBUG_BACKTRACE_PROVIDE_OBJECT) as $frame) {
$plugins []= $frame['object']->name;
if ($frame['object'] === $this) {
break;
}
}
throw new CircularException($plugins);
}
$this->visitedCircular = true;
foreach ($this->requiredBy as $node) {
$node->circular();
}
foreach ($this->extendedBy as $node) {
$node->circular();
}
$this->visitedCircular = false;
return $this;
}
/**
* Internal flattening.
*
* @param SplQueue<SplQueue<PluginInterface>> $splQueue Queue
*
* @return void
*/
private function flattenInternal(SplQueue $splQueue): void
{
$queue = $splQueue->top();
$this->plugin->enqueue($queue);
/** @var SplQueue<Node> */
$extendedBy = new SplQueue;
$prevNode = null;
foreach ($this->extendedBy as $node) {
if (\count($node->requires) + \count($node->extends) === 1) {
if ($prevNode instanceof self) {
$node->merge($prevNode);
}
$prevNode = $node;
} else {
$extendedBy->enqueue($node);
}
}
if ($prevNode) {
$extendedBy->enqueue($prevNode);
}
/** @var SplQueue<Node> */
$requiredBy = new SplQueue;
$prevNode = null;
foreach ($this->requiredBy as $node) {
if (\count($node->requires) + \count($node->extends) === 1) {
if ($prevNode instanceof self) {
$node->merge($prevNode);
}
$prevNode = $node;
} else {
$requiredBy->enqueue($node);
}
}
if ($prevNode) {
$requiredBy->enqueue($prevNode);
}
foreach ($extendedBy as $node) {
$node->extends->detach($this);
if (\count($node->extends) + \count($node->requires) === 0) {
$node->flattenInternal($splQueue);
}
}
foreach ($requiredBy as $node) {
$node->requires->detach($this);
if (\count($node->extends) + \count($node->requires) === 0) {
$splQueue->enqueue(new SplQueue);
$node->flattenInternal($splQueue);
}
}
}
}

View File

@ -25,10 +25,10 @@ class PackageContext
$this->packages[$package] = null;
}
/**
* Merge two contexts
* Merge two contexts.
*
* @param self $other Other context
*
*
* @return self New context
*/
public function merge(self $other): self
@ -38,12 +38,12 @@ class PackageContext
}
/**
* Get package list
* Get package list.
*
* @return array
*/
public function getPackages(): array
{
return array_values($this->packages);
return \array_values($this->packages);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Phabel\PluginGraph;
use Phabel\PluginInterface;
use SplQueue;
/**
* Representation of multiple plugins+configs.
*/
class Plugin
{
/**
* Plugin configs, indexed by plugin name.
*
* @var array<class-string<PluginInterface>, array[]>
*/
private array $plugins = [];
/**
* Constructor.
*
* @param class-string<PluginInterface> $plugin Plugin
* @param array $config Config
*/
public function __construct(string $plugin, array $config)
{
$this->plugins[$plugin] = [$config];
}
/**
* Merge with other plugins.
*
* @param self $other Plugins
*
* @return void
*/
public function merge(self $other): void
{
foreach ($other->plugins as $plugin => $configs) {
if (isset($this->plugins[$plugin])) {
$this->plugins[$plugin] = \array_merge($this->plugins[$plugin], $configs);
} else {
$this->plugins[$plugin] = $configs;
}
}
}
/**
* Enqueue plugins.
*
* @param SplQueue<PluginInterface> $queue
*
* @return void
*/
public function enqueue(SplQueue $queue): void
{
foreach ($this->plugins as $plugin => $configs) {
foreach ($plugin::mergeConfigs(...$configs) as $config) {
$pluginObj = new $plugin;
$pluginObj->setConfigArray($config);
$queue->enqueue($pluginObj);
}
}
}
}

View File

@ -16,7 +16,15 @@ interface PluginInterface
*
* @psalm-return class-string<PluginInterface>[]|array<class-string<PluginInterface>, array>
*/
public static function needs(): array;
public static function runAfter(): array;
/**
* Specify which plugins should run before this plugin.
*
* @return array Plugin name(s)
*
* @psalm-return class-string<PluginInterface>[]|array<class-string<PluginInterface>, array>
*/
public static function runBefore(): array;
/**
* Specify which plugins does this plugin extend.
*
@ -28,7 +36,14 @@ interface PluginInterface
*
* @psalm-return class-string<PluginInterface>[]|array<class-string<PluginInterface>, array>
*/
public static function extends(): array;
public static function runWithBefore(): array;
/**
*
* @return array Plugin name(s)
*
* @psalm-return class-string<PluginInterface>[]|array<class-string<PluginInterface>, array>
*/
public static function runWithAfter(): array;
/**
* Specify a list of composer dependencies.
@ -36,6 +51,14 @@ interface PluginInterface
* @return array
*/
public static function composerRequires(): array;
/**
* Set configuration array.
*
* @param array $config
*
* @return void
*/
public function setConfigArray(array $config): void;
/**
* Get configuration key.
*
@ -66,7 +89,7 @@ interface PluginInterface
public static function mergeConfigs(array ...$configs): array;
/**
* Split configuration.
*
*
* For example, if you have a configuration that enables feature A, B and C, return three configuration arrays each enabling ONLY A, only B and only C.
* This is used for optimizing the AST traversing process during resolution of the plugin graph.
*

View File

@ -53,7 +53,7 @@ class AnonymousClassReplacer extends NodeVisitorAbstract
*/
public function afterTraverse(array $nodes)
{
if (count($this->anonymousClassNodes) === 0) {
if (\count($this->anonymousClassNodes) === 0) {
return $nodes;
}
@ -101,15 +101,15 @@ class AnonymousClassReplacer extends NodeVisitorAbstract
* @param array $nodes
* @param $hookIndex
* @param $anonymousClassStatements
*
*
* @return array
*/
protected function moveAnonymousClassesToHook(array $nodes, $hookIndex, $anonymousClassStatements)
{
$preStatements = array_slice($nodes, 0, $hookIndex);
$postStatements = array_slice($nodes, $hookIndex);
$preStatements = \array_slice($nodes, 0, $hookIndex);
$postStatements = \array_slice($nodes, $hookIndex);
return array_merge($preStatements, $anonymousClassStatements, $postStatements);
return \array_merge($preStatements, $anonymousClassStatements, $postStatements);
}
/**

View File

@ -12,10 +12,10 @@ use PhpParser\Node\Name;
class ClosureCallReplacer extends Plugin
{
/**
* Replace composite function calls
* Replace composite function calls.
*
* @param FuncCall $node Function call
*
*
* @return StaticCall|null
*/
public function enter(FuncCall $node): ?StaticCall

View File

@ -14,10 +14,10 @@ use PhpParser\Node\Stmt\Const_;
class DefineArrayReplacer extends Plugin
{
/**
* Convert define() arrays into const arrays
* Convert define() arrays into const arrays.
*
* @param FuncCall $node Node
*
*
* @return Const_|null
*/
public function enter(FuncCall $node): ?Const_

View File

@ -11,10 +11,10 @@ use PhpParser\Node\Stmt\UseUse;
class GroupUseReplacer extends Plugin
{
/**
* Replace group use with multiple use statements
* Replace group use with multiple use statements.
*
* @param GroupUse $node Group use statement
*
*
* @return Use_[]
*/
public function leave(GroupUse $node): array

View File

@ -12,7 +12,7 @@ class ScalarTypeHintsRemover extends Plugin
*
* @return array
*/
public static function needs(): array
public static function runAfter(): array
{
return [
TypeHintStripper::class => [

View File

@ -56,18 +56,18 @@ class ThrowableReplacer extends Plugin
{
foreach ($node->catches as $catch) {
$alreadyHasError = false;
$needs = false;
$runAfter = false;
foreach ($catch->types as &$type) {
if ($type instanceof FullyQualified &&
$type->getLast() === "Error") {
$alreadyHasError = true;
}
if ($this->isThrowable($type)) {
$needs = true;
$runAfter = true;
$type = new FullyQualified('Exception');
}
}
if ($needs && !$alreadyHasError) {
if ($runAfter && !$alreadyHasError) {
$catch->types[] = new FullyQualified('Error');
}
}
@ -78,7 +78,7 @@ class ThrowableReplacer extends Plugin
*
* @return array
*/
public static function extends(): array
public static function runWithBefore(): array
{
return [
TypeHintStripper::class => [

View File

@ -4,7 +4,6 @@ namespace Phabel\Target\Php71;
use Phabel\Plugin;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\List_;
use PhpParser\Node\Stmt\Foreach_;
@ -110,7 +109,7 @@ class ListKey extends Plugin
/**
* {@inheritDoc}
*/
public static function needs(): array
public static function runAfter(): array
{
return [ArrayList::class];
}

View File

@ -12,7 +12,7 @@ class MultipleCatchReplacer extends Plugin
{
/**
* Replace compound catches.
*
*
* Do this while leaving to avoid re-iterating uselessly on duplicated code.
*
* @param TryCatch $node Catch stmt
@ -36,13 +36,13 @@ class MultipleCatchReplacer extends Plugin
$node->catches = $catches;
}
/**
* Extends throwable replacer
* Extends throwable replacer.
*
* @return string
*
*
* @psalm-return class-string
*/
public static function extends(): string
public static function runWithBefore(): string
{
return [ThrowableReplacer::class];
}

View File

@ -8,11 +8,11 @@ use Phabel\Plugin\TypeHintStripper;
class NullableTypeRemover extends Plugin
{
/**
* Remove nullable typehint
* Remove nullable typehint.
*
* @return array
*/
public static function needs(): array
public static function runAfter(): array
{
return [
TypeHintStripper::class => [

View File

@ -4,126 +4,4 @@ namespace Phabel;
class Traverser
{
/**
* Plugins by level.
*
* @var array<int, array<class-string<PluginInterface>, array>>
*/
private array $plugins = [];
/**
* Excluded plugins by level.
*
* @var array<int, array<class-string<PluginInterface>, bool>>
*/
private array $excludedPlugins = [];
/**
* Files indexed by level.
*
* @var array<int, string[]>
*/
private array $files = [];
/**
* Add plugin at a certain dependency level.
*
* @param class-string<PluginInterface> $plugin Plugin to add
* @param array $config Plugin configuration
* @param integer $level Dependency level
*
* @return void
*/
public function addPlugin(string $plugin, array $config, int $level): void
{
$this->plugins[$level][$plugin] = $config;
}
/**
* Exclude plugin at a certain dependency level.
*
* @param class-string<PluginInterface> $plugin Plugin to exclude
* @param bool $excludeNextLevels Whether to exclude plugin from next levels, too
* @param integer $level Dependency level
*
* @return void
*/
public function excludePlugin(string $plugin, bool $excludeNextLevels, int $level): void
{
$this->excludedPlugins[$level][$plugin] = $excludeNextLevels;
}
/**
* Add file.
*
* @param string $path
* @param integer $level
* @return void
*/
public function addFile(string $path, int $level): void
{
if (\in_array($path, $this->files[$level])) {
return;
}
$this->files[$level][] = $path;
}
/**
* Start traversing files.
*
* @return void
*/
public function traverse(): void
{
}
private function resolveCycle(string $class, bool $need, array &$stack): void
{
$allPlugins = [];
foreach ($this->plugins as $level => $plugins) {
foreach ($plugins as $plugin => $config) {
$needs = self::simpleNeeds($plugin);
foreach ($needs as $class => $config) {
if ()
}
}
}
}
/**
* Simplify need requirements
*
* @param class-string<PluginInterface> $class Class to resolve
*
* @return array<class-string<PluginInterface>, array>
*/
private static function simpleNeeds(string $class): array
{
/**
* @var array<class-string<PluginInterface>, array>[]
*/
static $cache = [];
if (isset($cache[$class])) {
return $cache[$class];
}
$needs = $class::needs();
return $cache[$class] = isset($needs[0]) ? array_fill_keys($needs, []) : $needs
}
/**
* Simplify extend requirements
*
* @param class-string<PluginInterface> $class Class to resolve
*
* @return array<class-string<PluginInterface>, array>
*/
private static function simpleExtends(string $class): array
{
/**
* @var array<class-string<PluginInterface>, array>[]
*/
static $cache = [];
if (isset($cache[$class])) {
return $cache[$class];
}
$needs = $class::extends();
return $cache[$class] = isset($needs[0]) ? array_fill_keys($needs, []) : $needs
}
}

View File

@ -1,99 +0,0 @@
<?php
namespace Phabel;
class TraverserConfig
{
/**
* Plugin configurations, indexed by level.
*
* @var array<int, array<class-string<PluginInterface>, array<0: bool, 1: array>[]>>
*/
private array $plugins = [];
/**
* Excluded plugins by level.
*
* @var array<int, array<class-string<PluginInterface>, array[]>>
*/
private array $excludedPlugins = [];
/**
* Files indexed by level.
*
* @var array<int, string[]>
*/
private array $files = [];
/**
* Add plugin at a certain dependency level.
*
* @param class-string<PluginInterface> $plugin Plugin to add
* @param array $config Plugin configuration
* @param integer $level Dependency level
*
* @return void
*/
public function addPlugin(string $plugin, array $config, int $level): void
{
$this->plugins[$level][$plugin] []= $config;
}
/**
* Get all plugins at a certain level.
*
* @param integer $level Level
*
* @return array
*/
public function getPlugins(int $level): array
{
return $this->plugins[$level];
}
/**
* Exclude plugin at a certain dependency level.
*
* @param class-string<PluginInterface> $plugin Plugin to exclude
* @param bool $excludeNextLevels Whether to exclude plugin from next levels, too
* @param integer $level Dependency level
*
* @return void
*/
public function excludePlugin(string $plugin, bool $excludeNextLevels, int $level): void
{
$this->excludedPlugins[$level][$plugin][] = $excludeNextLevels;
}
/**
* Get final plugin array.
*
* @return array<int, array<class-string<PluginInterface, array>>>
*/
public function final(): array
{
\ksort($this->plugins);
\ksort($this->excludedPlugins);
return [];
}
/**
* Trickle plugins down the dependency tree.
*
* @return void Insert 1% joke
*/
private function trickleDown(): void
{
$met = [];
$maxLevel = \array_key_last($this->plugins);
foreach ($this->plugins as $level => $plugins) {
foreach ($plugins as $plugin => $config) {
$found = false;
for ($checkLevel = $level + 1; $checkLevel <= $maxLevel; $checkLevel++) {
if (isset($this->plugins[$checkLevel][$plugin])
&& $this->plugins[$checkLevel][$plugin] !== $config) {
$found = true;
break;
}
$this->plugins[$checkLevel][$plugin] = $config;
}
$checkLevel--;
}
}
}
}