Implement traverser

This commit is contained in:
Daniil Gentili 2020-08-13 21:34:59 +02:00
parent f1d8722889
commit 128977ef7e
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
20 changed files with 286 additions and 53 deletions

View File

@ -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",

View File

@ -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 = [];
}
/**

View File

@ -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

View File

@ -20,7 +20,7 @@ use SplStack;
/**
* Enable memoization of results based on a parameter.
*
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class Memoization

View File

@ -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

View File

@ -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
{

View File

@ -4,7 +4,7 @@ namespace Phabel\PluginGraph;
/**
* Graph API wrapper.
*
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class Graph

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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

View File

@ -7,7 +7,7 @@ use SplQueue;
/**
* Representation of multiple plugins+configs.
*
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class Plugins

View File

@ -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;

View File

@ -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 [

View File

@ -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
{

View File

@ -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 [

19
src/Target/Php72.php Normal file
View File

@ -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' => '*'];
}
}

19
src/Target/Php73.php Normal file
View File

@ -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' => '*'];
}
}

19
src/Target/Php74.php Normal file
View File

@ -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' => '*'];
}
}

View File

@ -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;
}
}
}
}
}
}

View File

@ -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])));