Implement traverser
This commit is contained in:
parent
f1d8722889
commit
128977ef7e
|
@ -4,8 +4,7 @@
|
|||
"type": "project",
|
||||
"require": {
|
||||
"nikic/php-parser": "^4.7",
|
||||
"composer-plugin-api": "^1|^2",
|
||||
"amphp/byte-stream": "^1.7"
|
||||
"composer-plugin-api": "^1|^2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
|
|
|
@ -7,6 +7,10 @@ use Composer\Package\PackageInterface;
|
|||
use Composer\Repository\ComposerRepository;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
|
||||
/**
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Repository extends ComposerRepository
|
||||
{
|
||||
/**
|
||||
|
@ -25,6 +29,7 @@ class Repository extends ComposerRepository
|
|||
public function __construct(ComposerRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->packages = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,7 +25,7 @@ abstract class Plugin implements PluginInterface
|
|||
*/
|
||||
private array $config = [];
|
||||
/**
|
||||
* Package context
|
||||
* Package context.
|
||||
*/
|
||||
private PackageContext $ctx;
|
||||
/**
|
||||
|
@ -39,10 +39,10 @@ abstract class Plugin implements PluginInterface
|
|||
$this->config = $config;
|
||||
}
|
||||
/**
|
||||
* Set package context
|
||||
* Set package context.
|
||||
*
|
||||
* @param PackageContext $ctx Ctx
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPackageContext(PackageContext $ctx): void
|
||||
|
@ -50,7 +50,7 @@ abstract class Plugin implements PluginInterface
|
|||
$this->ctx = $ctx;
|
||||
}
|
||||
/**
|
||||
* Get package context
|
||||
* Get package context.
|
||||
*
|
||||
* @return PackageContext
|
||||
*/
|
||||
|
@ -59,10 +59,10 @@ abstract class Plugin implements PluginInterface
|
|||
return $this->ctx;
|
||||
}
|
||||
/**
|
||||
* Check if plugin should run
|
||||
* Check if plugin should run.
|
||||
*
|
||||
* @param string $package Package name
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldRun(string $package): bool
|
||||
|
@ -70,10 +70,10 @@ abstract class Plugin implements PluginInterface
|
|||
return $this->ctx->has($package);
|
||||
}
|
||||
/**
|
||||
* Check if plugin should run
|
||||
* Check if plugin should run.
|
||||
*
|
||||
* @param string $file File name
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldRunFile(string $file): bool
|
||||
|
@ -127,11 +127,11 @@ abstract class Plugin implements PluginInterface
|
|||
$node = self::replaceType($node, $class, $propertyMap);
|
||||
}
|
||||
/**
|
||||
* Create variable assignment
|
||||
* Create variable assignment.
|
||||
*
|
||||
* @param Variable $name Variable
|
||||
* @param Expr $expression Expression
|
||||
*
|
||||
*
|
||||
* @return Expression
|
||||
*/
|
||||
public static function assign(Variable $name, Expr $expression): Expression
|
||||
|
|
|
@ -20,7 +20,7 @@ use SplStack;
|
|||
|
||||
/**
|
||||
* Enable memoization of results based on a parameter.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Memoization
|
||||
|
|
|
@ -11,7 +11,7 @@ use PhpParser\Node\UnionType;
|
|||
|
||||
/**
|
||||
* Replace all usages of a certain type in typehints.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class TypeHintStripper extends Plugin
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
namespace Phabel;
|
||||
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Caches plugin information.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class PluginCache
|
||||
|
@ -36,9 +38,13 @@ class PluginCache
|
|||
self::$leaveMethods[$plugin] = [];
|
||||
foreach (\get_class_methods($plugin) as $method) {
|
||||
if (\str_starts_with($method, 'enter')) {
|
||||
self::$enterMethods[$plugin] []= $method;
|
||||
$reflection = new ReflectionMethod($plugin, $method);
|
||||
$type = $reflection->getParameters()[0]->getType()->getName();
|
||||
self::$enterMethods[$plugin][$type] []= $method;
|
||||
} elseif (\str_starts_with($method, 'leave')) {
|
||||
self::$leaveMethods[$plugin] []= $method;
|
||||
$reflection = new ReflectionMethod($plugin, $method);
|
||||
$type = $reflection->getParameters()[0]->getType()->getName();
|
||||
self::$leaveMethods[$plugin][$type] []= $method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +68,7 @@ class PluginCache
|
|||
*
|
||||
* @param class-string<PluginInterface> $plugin Plugin name
|
||||
*
|
||||
* @return string[]
|
||||
* @return array<string, string[]>
|
||||
*/
|
||||
public static function enterMethods(string $plugin): array
|
||||
{
|
||||
|
@ -74,7 +80,7 @@ class PluginCache
|
|||
*
|
||||
* @param class-string<PluginInterface> $plugin Plugin name
|
||||
*
|
||||
* @return string[]
|
||||
* @return array<string, string[]>
|
||||
*/
|
||||
public static function leaveMethods(string $plugin): array
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace Phabel\PluginGraph;
|
|||
|
||||
/**
|
||||
* Graph API wrapper.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Graph
|
||||
|
|
|
@ -79,7 +79,7 @@ class GraphInternal
|
|||
{
|
||||
$configStr = \var_export($config, true);
|
||||
if (isset($this->plugins[$plugin][$configStr])) {
|
||||
return $this->plugins[$plugin][$configStr];
|
||||
return $this->plugins[$plugin][$configStr]->addPackages($ctx);
|
||||
}
|
||||
$this->plugins[$plugin][$configStr] = $node = new Node($this, $ctx);
|
||||
$this->unlinkedNodes->attach($node);
|
||||
|
|
|
@ -9,7 +9,7 @@ use SplQueue;
|
|||
|
||||
/**
|
||||
* Represents a plugin with a certain configuration.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Node
|
||||
|
@ -290,7 +290,7 @@ class Node
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
foreach ($extendedBy as $node) {
|
||||
$node->extends->detach($this);
|
||||
if (\count($node->extends) + \count($node->requires) === 0) {
|
||||
|
@ -305,4 +305,16 @@ class Node
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add packages from package context.
|
||||
*
|
||||
* @param PackageContext $ctx Package context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPackages(PackageContext $ctx): void
|
||||
{
|
||||
$this->packageContext->merge($ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace Phabel\PluginGraph;
|
|||
|
||||
/**
|
||||
* List of packages associated with plugin.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class PackageContext
|
||||
|
@ -40,10 +40,10 @@ class PackageContext
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if a package is present in the package context
|
||||
* Check if a package is present in the package context.
|
||||
*
|
||||
* @param string $package Package
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has(string $package): bool
|
||||
|
|
|
@ -7,7 +7,7 @@ use SplQueue;
|
|||
|
||||
/**
|
||||
* Representation of multiple plugins+configs.
|
||||
*
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Plugins
|
||||
|
|
|
@ -65,32 +65,32 @@ interface PluginInterface
|
|||
*/
|
||||
public function setConfigArray(array $config): void;
|
||||
/**
|
||||
* Set package context
|
||||
* Set package context.
|
||||
*
|
||||
* @param PackageContext $ctx Ctx
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPackageContext(PackageContext $ctx): void;
|
||||
/**
|
||||
* Get package context
|
||||
* Get package context.
|
||||
*
|
||||
* @return PackageContext
|
||||
*/
|
||||
public function getPackageContext(): PackageContext;
|
||||
/**
|
||||
* Check if plugin should run
|
||||
* Check if plugin should run.
|
||||
*
|
||||
* @param string $package Package name
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldRun(string $package): bool;
|
||||
/**
|
||||
* Check if plugin should run
|
||||
* Check if plugin should run.
|
||||
*
|
||||
* @param string $file File name
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldRunFile(string $file): bool;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\PluginInterface;
|
||||
use Phabel\Target\Php70\AnonymousClassReplacer;
|
||||
use Phabel\Target\Php70\ClosureCallReplacer;
|
||||
use Phabel\Target\Php70\CompoundAccess;
|
||||
|
@ -17,10 +16,17 @@ use Phabel\Target\Php70\StrictTypesDeclareStatementRemover;
|
|||
use Phabel\Target\Php70\ThrowableReplacer;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 7.0 and run on PHP 5.6 and below.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Php70 extends Plugin
|
||||
{
|
||||
public static function composerRequires(): array
|
||||
{
|
||||
return ['symfony/polyfill-php70' => '*'];
|
||||
}
|
||||
public static function runWithAfter(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -4,11 +4,6 @@ namespace Phabel\Target\Php70;
|
|||
|
||||
use Phabel\Plugin;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Declare_;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use Spatie\Php7to5\Converter;
|
||||
use Spatie\Php7to5\Exceptions\InvalidPhpCode;
|
||||
|
||||
class AnonymousClassReplacer extends Plugin
|
||||
{
|
||||
|
|
|
@ -3,18 +3,6 @@
|
|||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
use Phabel\PluginInterface;
|
||||
use Phabel\Target\Php70\AnonymousClassReplacer;
|
||||
use Phabel\Target\Php70\ClosureCallReplacer;
|
||||
use Phabel\Target\Php70\CompoundAccess;
|
||||
use Phabel\Target\Php70\DefineArrayReplacer;
|
||||
use Phabel\Target\Php70\GroupUseReplacer;
|
||||
use Phabel\Target\Php70\NullCoalesceReplacer;
|
||||
use Phabel\Target\Php70\ReservedNameReplacer;
|
||||
use Phabel\Target\Php70\ScalarTypeHintsRemover;
|
||||
use Phabel\Target\Php70\SpaceshipOperatorReplacer;
|
||||
use Phabel\Target\Php70\StrictTypesDeclareStatementRemover;
|
||||
use Phabel\Target\Php70\ThrowableReplacer;
|
||||
use Phabel\Target\Php71\ArrayList;
|
||||
use Phabel\Target\Php71\ClassConstantVisibilityModifiersRemover;
|
||||
use Phabel\Target\Php71\ListKey;
|
||||
|
@ -22,10 +10,17 @@ use Phabel\Target\Php71\MultipleCatchReplacer;
|
|||
use Spatie\Php7to5\NodeVisitors\NullableTypeRemover;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 7.1 and run on PHP 7.0 and below.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Php71 extends Plugin
|
||||
{
|
||||
public static function composerRequires(): array
|
||||
{
|
||||
return ['symfony/polyfill-php70' => '*'];
|
||||
}
|
||||
public static function runWithAfter(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 7.2 and run on PHP 7.1 and below.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Php72 extends Plugin
|
||||
{
|
||||
public static function composerRequires(): array
|
||||
{
|
||||
return ['symfony/polyfill-php72' => '*'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 7.3 and run on PHP 7.2 and below.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Php73 extends Plugin
|
||||
{
|
||||
public static function composerRequires(): array
|
||||
{
|
||||
return ['symfony/polyfill-php72' => '*'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Phabel\Target;
|
||||
|
||||
use Phabel\Plugin;
|
||||
|
||||
/**
|
||||
* Makes changes necessary to polyfill PHP 7.4 and run on PHP 7.3 and below.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @license MIT
|
||||
*/
|
||||
class Php74 extends Plugin
|
||||
{
|
||||
public static function composerRequires(): array
|
||||
{
|
||||
return ['symfony/polyfill-php72' => '*'];
|
||||
}
|
||||
}
|
|
@ -2,9 +2,167 @@
|
|||
|
||||
namespace Phabel;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Parser;
|
||||
use PhpParser\ParserFactory;
|
||||
use SplQueue;
|
||||
|
||||
/**
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Traverser
|
||||
{
|
||||
/**
|
||||
* Plugin queue.
|
||||
*
|
||||
* @return SplQueue<SplQueue<Plugin>>
|
||||
*/
|
||||
private SplQueue $queue;
|
||||
/**
|
||||
* Parser instance.
|
||||
*/
|
||||
private Parser $parser;
|
||||
/**
|
||||
* Plugin queue for specific package.
|
||||
*
|
||||
* @return SplQueue<SplQueue<Plugin>>
|
||||
*/
|
||||
private SplQueue $packageQueue;
|
||||
/**
|
||||
* AST traverser.
|
||||
*
|
||||
* @return SplQueue<SplQueue<Plugin>> $queue Plugin queue
|
||||
*/
|
||||
public function __construct(SplQueue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
$this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
}
|
||||
/**
|
||||
* Set package name.
|
||||
*
|
||||
* @param string $package Package name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPackage(string $package): void
|
||||
{
|
||||
$this->packageQueue = new SplQueue;
|
||||
$newQueue = new SplQueue;
|
||||
foreach ($this->queue as $queue) {
|
||||
if ($newQueue->count()) {
|
||||
$this->packageQueue->enqueue($newQueue);
|
||||
$newQueue = new SplQueue;
|
||||
}
|
||||
/** @var Plugin */
|
||||
foreach ($queue as $plugin) {
|
||||
if ($plugin->shouldRun($package)) {
|
||||
$newQueue->enqueue($plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($newQueue->count()) {
|
||||
$this->packageQueue->enqueue($newQueue);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Traverse AST of file.
|
||||
*
|
||||
* @param string $file File
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function traverse(string $file): void
|
||||
{
|
||||
/** @var SplQueue<SplQueue<Plugin>> */
|
||||
$reducedQueue = new SplQueue;
|
||||
$newQueue = new SplQueue;
|
||||
foreach ($this->packageQueue ?? $this->queue as $queue) {
|
||||
if ($newQueue->count()) {
|
||||
$reducedQueue->enqueue($newQueue);
|
||||
$newQueue = new SplQueue;
|
||||
}
|
||||
/** @var Plugin */
|
||||
foreach ($queue as $plugin) {
|
||||
if ($plugin->shouldRunFile($file)) {
|
||||
$newQueue->enqueue($plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($newQueue->count()) {
|
||||
$reducedQueue->enqueue($newQueue);
|
||||
} elseif (!$reducedQueue->count()) {
|
||||
return;
|
||||
}
|
||||
$ast = $this->parser->parse(\file_get_contents($file));
|
||||
foreach ($reducedQueue as $queue) {
|
||||
$this->traverseArray($ast, $queue);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Traverse array of nodes.
|
||||
*
|
||||
* @param Node[] $nodes Nodes
|
||||
* @param SplQueue<Plugin> $plugins Plugins
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function traverseArray(array &$nodes, SplQueue $plugins): void
|
||||
{
|
||||
foreach ($nodes as &$node) {
|
||||
$this->traverseNode($node, $plugins);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Traverse node.
|
||||
*
|
||||
* @param Node &$node Node
|
||||
* @param SplQueue<Plugin> $plugins Plugins
|
||||
* @return void
|
||||
*/
|
||||
public function traverseNode(Node &$node, SplQueue $plugins): void
|
||||
{
|
||||
foreach ($plugins as $plugin) {
|
||||
foreach (PluginCache::enterMethods(\get_class($plugin)) as $type => $methods) {
|
||||
if (!$node instanceof $type) {
|
||||
continue;
|
||||
}
|
||||
foreach ($methods as $method) {
|
||||
$result = $plugin->{$method}($node);
|
||||
if ($result instanceof Node) {
|
||||
if (!$result instanceof $node) {
|
||||
$node = $result;
|
||||
continue 2;
|
||||
}
|
||||
$node = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($node->getSubNodeNames() as $name) {
|
||||
$subNode = &$node->{$name};
|
||||
if (\is_array($subNode)) {
|
||||
$this->traverseArray($subNode, $plugins);
|
||||
} else {
|
||||
$this->traverseNode($subNode, $plugins);
|
||||
}
|
||||
}
|
||||
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);
|
||||
if ($result instanceof Node) {
|
||||
if (!$result instanceof $node) {
|
||||
$node = $result;
|
||||
continue 2;
|
||||
}
|
||||
$node = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,4 @@ if ($argc < 2) {
|
|||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
|
||||
var_dump($parser->parse(file_get_contents($argv[1])));
|
||||
\var_dump($parser->parse(\file_get_contents($argv[1])));
|
||||
|
|
Loading…
Reference in New Issue