diff --git a/.gitignore b/.gitignore index 3a9875b..bef310b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ -/vendor/ +.vscode +build composer.lock +phpunit.xml +vendor +.php_cs.cache +coverage diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..9459eed --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,13 @@ +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; diff --git a/composer.json b/composer.json index b7531f4..e5503df 100644 --- a/composer.json +++ b/composer.json @@ -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" + } +} \ No newline at end of file diff --git a/src/Context.php b/src/Context.php index 8abf4dc..86120c6 100644 --- a/src/Context.php +++ b/src/Context.php @@ -4,5 +4,4 @@ namespace Phabel; class Context { - -} \ No newline at end of file +} diff --git a/src/Context/UniqueId.php b/src/Context/UniqueId.php index 1bd5537..622f483 100644 --- a/src/Context/UniqueId.php +++ b/src/Context/UniqueId.php @@ -6,5 +6,4 @@ use Phabel\Plugin; class UniqueId extends Plugin { - -} \ No newline at end of file +} diff --git a/src/Plugin.php b/src/Plugin.php index 0ebe534..e93a3e7 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -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 []; } diff --git a/src/Plugin/Amp/YieldFromReplacer.php b/src/Plugin/Amp/YieldFromReplacer.php index fc3f65a..327fb9b 100644 --- a/src/Plugin/Amp/YieldFromReplacer.php +++ b/src/Plugin/Amp/YieldFromReplacer.php @@ -4,7 +4,6 @@ namespace Spatie\Php7to5\NodeVisitors; use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -use PhpParser\ParserFactory; class YieldFromReplacer extends NodeVisitorAbstract { diff --git a/src/Plugin/Amp/YieldReturnDetector.php b/src/Plugin/Amp/YieldReturnDetector.php index 05d3f49..dd2c537 100644 --- a/src/Plugin/Amp/YieldReturnDetector.php +++ b/src/Plugin/Amp/YieldReturnDetector.php @@ -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); } } } diff --git a/src/Plugin/Amp/YieldReturnReplacer.php b/src/Plugin/Amp/YieldReturnReplacer.php index e79c9a3..7920031 100644 --- a/src/Plugin/Amp/YieldReturnReplacer.php +++ b/src/Plugin/Amp/YieldReturnReplacer.php @@ -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; } diff --git a/src/PluginCache.php b/src/PluginCache.php new file mode 100644 index 0000000..e8196fb --- /dev/null +++ b/src/PluginCache.php @@ -0,0 +1,157 @@ +, string[]> + */ + private static array $enterMethods = []; + /** + * Leave method names. + * + * @var array, string[]> + */ + private static array $leaveMethods = []; + + /** + * Cache method information. + * + * @param class-string $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 $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 $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 $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 $plugin Plugin + * + * @return array, array> + */ + public static function runBefore(string $plugin): array + { + /** @var array, array, array>> */ + static $cache = []; + if (isset($cache[$plugin])) { + return $cache[$plugin]; + } + return $cache[$plugin] = self::simplify($plugin::runBefore()); + } + /** + * Get runAfter requirements. + * + * @param class-string $plugin Plugin + * + * @return array, array> + */ + public static function runAfter(string $plugin): array + { + /** @var array, array, array>> */ + static $cache = []; + if (isset($cache[$plugin])) { + return $cache[$plugin]; + } + return $cache[$plugin] = self::simplify($plugin::runAfter()); + } + /** + * Get runWithBefore requirements. + * + * @param class-string $plugin Plugin + * + * @return array, array> + */ + public static function runWithBefore(string $plugin): array + { + /** @var array, array, array>> */ + static $cache = []; + if (isset($cache[$plugin])) { + return $cache[$plugin]; + } + return $cache[$plugin] = self::simplify($plugin::runWithBefore()); + } + /** + * Get runWithAfter requirements. + * + * @param class-string $plugin Plugin + * + * @return array, array> + */ + public static function runWithAfter(string $plugin): array + { + /** @var array, array, array>> */ + static $cache = []; + if (isset($cache[$plugin])) { + return $cache[$plugin]; + } + return $cache[$plugin] = self::simplify($plugin::runWithAfter()); + } + /** + * Simplify requirements. + * + * @param (array, array>|class-string[]) $requirements Requirements + * + * @return array, array> + */ + private static function simplify(array $requirements): array + { + return isset($requirements[0]) ? \array_fill_keys($requirements, []) : $requirements; + } +} diff --git a/src/PluginGraph/CircularException.php b/src/PluginGraph/CircularException.php new file mode 100644 index 0000000..08818a0 --- /dev/null +++ b/src/PluginGraph/CircularException.php @@ -0,0 +1,35 @@ +[] + */ + private array $plugins = []; + /** + * Constructor. + * + * @param class-string[] $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[] + */ + public function getPlugins(): array + { + return $this->plugins; + } +} diff --git a/src/PluginGraph/Graph.php b/src/PluginGraph/Graph.php index 2956fab..b7826c8 100644 --- a/src/PluginGraph/Graph.php +++ b/src/PluginGraph/Graph.php @@ -2,24 +2,22 @@ namespace Phabel\PluginGraph; -use Phabel\PluginInterface; - +/** + * Graph API wrapper. + */ class Graph { /** - * Plugin nodes, indexed by plugin name+config. - * - * @var array, array> + * 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 $plugin Plugin name - * - * @return Node + * @return SplQueue> */ - 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(); } } diff --git a/src/PluginGraph/GraphInternal.php b/src/PluginGraph/GraphInternal.php new file mode 100644 index 0000000..a26dc06 --- /dev/null +++ b/src/PluginGraph/GraphInternal.php @@ -0,0 +1,132 @@ +, array> + */ + private array $plugins = []; + + /** + * Package contexts. + * + * @var PackageContext[] + */ + private array $packageContexts = []; + + /** + * Stores list of Nodes that are not required by any other node. + * + * @var SplObjectStorage + */ + 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 $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 $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> + */ + 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(); + } +} diff --git a/src/PluginGraph/Node.php b/src/PluginGraph/Node.php index e370e8e..a16191f 100644 --- a/src/PluginGraph/Node.php +++ b/src/PluginGraph/Node.php @@ -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 */ - private array $config = []; + private string $name; /** * Associated package contexts. + * + * @var SplObjectStorage */ private SplObjectStorage $packageContexts; /** * Nodes that this node requires. * - * @var Node[] + * @var SplObjectStorage */ - private array $requires = []; + private SplObjectStorage $requires; /** * Nodes that this node extends. * - * @var Node[] + * @var SplObjectStorage */ - private array $extends = []; + private SplObjectStorage $extends; /** * Nodes that require this node. * - * @var Node[] + * @var SplObjectStorage */ - private array $requiredBy = []; + private SplObjectStorage $requiredBy; /** * Nodes that extend this node. * - * @var Node[] + * @var SplObjectStorage */ - 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, array>|class-string[]) $requirements Requirements + * @param self $node Node * - * @return array, 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> + */ + public function flatten(): SplQueue + { + /** @var SplQueue */ + $initQueue = new SplQueue; + + /** @var SplQueue> */ + $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 Queue + * + * @return void + */ + private function flattenInternal(SplQueue $splQueue): void + { + $queue = $splQueue->top(); + $this->plugin->enqueue($queue); + + /** @var SplQueue */ + $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 */ + $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); + } + } } } diff --git a/src/PluginGraph/PackageContext.php b/src/PluginGraph/PackageContext.php index afb1709..2255dd6 100644 --- a/src/PluginGraph/PackageContext.php +++ b/src/PluginGraph/PackageContext.php @@ -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); } } diff --git a/src/PluginGraph/Plugin.php b/src/PluginGraph/Plugin.php new file mode 100644 index 0000000..d303900 --- /dev/null +++ b/src/PluginGraph/Plugin.php @@ -0,0 +1,66 @@ +, array[]> + */ + private array $plugins = []; + + /** + * Constructor. + * + * @param class-string $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 $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); + } + } + } +} diff --git a/src/PluginInterface.php b/src/PluginInterface.php index e11d812..d889656 100644 --- a/src/PluginInterface.php +++ b/src/PluginInterface.php @@ -16,7 +16,15 @@ interface PluginInterface * * @psalm-return class-string[]|array, 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[]|array, array> + */ + public static function runBefore(): array; /** * Specify which plugins does this plugin extend. * @@ -28,7 +36,14 @@ interface PluginInterface * * @psalm-return class-string[]|array, array> */ - public static function extends(): array; + public static function runWithBefore(): array; + /** + * + * @return array Plugin name(s) + * + * @psalm-return class-string[]|array, 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. * diff --git a/src/Target/Php70/AnonymousClassReplacer.php b/src/Target/Php70/AnonymousClassReplacer.php index 8d2c885..54ac536 100644 --- a/src/Target/Php70/AnonymousClassReplacer.php +++ b/src/Target/Php70/AnonymousClassReplacer.php @@ -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); } /** diff --git a/src/Target/Php70/ClosureCallReplacer.php b/src/Target/Php70/ClosureCallReplacer.php index 1a13c4f..fac7b08 100644 --- a/src/Target/Php70/ClosureCallReplacer.php +++ b/src/Target/Php70/ClosureCallReplacer.php @@ -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 diff --git a/src/Target/Php70/DefineArrayReplacer.php b/src/Target/Php70/DefineArrayReplacer.php index 5155f6e..8a6eed8 100644 --- a/src/Target/Php70/DefineArrayReplacer.php +++ b/src/Target/Php70/DefineArrayReplacer.php @@ -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_ diff --git a/src/Target/Php70/GroupUseReplacer.php b/src/Target/Php70/GroupUseReplacer.php index 63ee680..ada1480 100644 --- a/src/Target/Php70/GroupUseReplacer.php +++ b/src/Target/Php70/GroupUseReplacer.php @@ -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 diff --git a/src/Target/Php70/ScalarTypeHintsRemover.php b/src/Target/Php70/ScalarTypeHintsRemover.php index 5ddaa8c..8653bb4 100644 --- a/src/Target/Php70/ScalarTypeHintsRemover.php +++ b/src/Target/Php70/ScalarTypeHintsRemover.php @@ -12,7 +12,7 @@ class ScalarTypeHintsRemover extends Plugin * * @return array */ - public static function needs(): array + public static function runAfter(): array { return [ TypeHintStripper::class => [ diff --git a/src/Target/Php70/ThrowableReplacer.php b/src/Target/Php70/ThrowableReplacer.php index d62c844..db56722 100644 --- a/src/Target/Php70/ThrowableReplacer.php +++ b/src/Target/Php70/ThrowableReplacer.php @@ -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 => [ diff --git a/src/Target/Php71/ListKey.php b/src/Target/Php71/ListKey.php index 730a622..4455574 100644 --- a/src/Target/Php71/ListKey.php +++ b/src/Target/Php71/ListKey.php @@ -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]; } diff --git a/src/Target/Php71/MultipleCatchReplacer.php b/src/Target/Php71/MultipleCatchReplacer.php index 68ff85a..d134483 100644 --- a/src/Target/Php71/MultipleCatchReplacer.php +++ b/src/Target/Php71/MultipleCatchReplacer.php @@ -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]; } diff --git a/src/Target/Php71/NullableTypeRemover.php b/src/Target/Php71/NullableTypeRemover.php index bd06bea..fe68fc9 100644 --- a/src/Target/Php71/NullableTypeRemover.php +++ b/src/Target/Php71/NullableTypeRemover.php @@ -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 => [ diff --git a/src/Traverser.php b/src/Traverser.php index 34b7854..9fbc448 100644 --- a/src/Traverser.php +++ b/src/Traverser.php @@ -4,126 +4,4 @@ namespace Phabel; class Traverser { - /** - * Plugins by level. - * - * @var array, array>> - */ - private array $plugins = []; - /** - * Excluded plugins by level. - * - * @var array, bool>> - */ - private array $excludedPlugins = []; - /** - * Files indexed by level. - * - * @var array - */ - private array $files = []; - /** - * Add plugin at a certain dependency level. - * - * @param class-string $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 $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 $class Class to resolve - * - * @return array, array> - */ - private static function simpleNeeds(string $class): array - { - /** - * @var array, 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 $class Class to resolve - * - * @return array, array> - */ - private static function simpleExtends(string $class): array - { - /** - * @var array, array>[] - */ - static $cache = []; - if (isset($cache[$class])) { - return $cache[$class]; - } - $needs = $class::extends(); - return $cache[$class] = isset($needs[0]) ? array_fill_keys($needs, []) : $needs - } } diff --git a/src/TraverserConfig.php b/src/TraverserConfig.php deleted file mode 100644 index 3093442..0000000 --- a/src/TraverserConfig.php +++ /dev/null @@ -1,99 +0,0 @@ -, array<0: bool, 1: array>[]>> - */ - private array $plugins = []; - /** - * Excluded plugins by level. - * - * @var array, array[]>> - */ - private array $excludedPlugins = []; - /** - * Files indexed by level. - * - * @var array - */ - private array $files = []; - /** - * Add plugin at a certain dependency level. - * - * @param class-string $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 $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>> - */ - 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--; - } - } - } -}