diff --git a/composer.json b/composer.json index bab37dc..d44297f 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/src/Composer/Repository.php b/src/Composer/Repository.php index d634c07..ee1a25d 100644 --- a/src/Composer/Repository.php +++ b/src/Composer/Repository.php @@ -7,6 +7,10 @@ use Composer\Package\PackageInterface; use Composer\Repository\ComposerRepository; use Composer\Semver\Constraint\ConstraintInterface; +/** + * @author Daniil Gentili + * @license MIT + */ class Repository extends ComposerRepository { /** @@ -25,6 +29,7 @@ class Repository extends ComposerRepository public function __construct(ComposerRepository $repository) { $this->repository = $repository; + $this->packages = []; } /** diff --git a/src/Plugin.php b/src/Plugin.php index 303605b..e9367cf 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -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 diff --git a/src/Plugin/Memoization.php b/src/Plugin/Memoization.php index dfa3b06..1926d87 100644 --- a/src/Plugin/Memoization.php +++ b/src/Plugin/Memoization.php @@ -20,7 +20,7 @@ use SplStack; /** * Enable memoization of results based on a parameter. - * + * * @author Daniil Gentili */ class Memoization diff --git a/src/Plugin/TypeHintStripper.php b/src/Plugin/TypeHintStripper.php index 6fae0e3..a35f0ff 100644 --- a/src/Plugin/TypeHintStripper.php +++ b/src/Plugin/TypeHintStripper.php @@ -11,7 +11,7 @@ use PhpParser\Node\UnionType; /** * Replace all usages of a certain type in typehints. - * + * * @author Daniil Gentili */ class TypeHintStripper extends Plugin diff --git a/src/PluginCache.php b/src/PluginCache.php index f7f5667..ccabc40 100644 --- a/src/PluginCache.php +++ b/src/PluginCache.php @@ -2,9 +2,11 @@ namespace Phabel; +use ReflectionMethod; + /** * Caches plugin information. - * + * * @author Daniil Gentili */ 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 $plugin Plugin name * - * @return string[] + * @return array */ public static function enterMethods(string $plugin): array { @@ -74,7 +80,7 @@ class PluginCache * * @param class-string $plugin Plugin name * - * @return string[] + * @return array */ public static function leaveMethods(string $plugin): array { diff --git a/src/PluginGraph/Graph.php b/src/PluginGraph/Graph.php index b0cdbb2..b656c6b 100644 --- a/src/PluginGraph/Graph.php +++ b/src/PluginGraph/Graph.php @@ -4,7 +4,7 @@ namespace Phabel\PluginGraph; /** * Graph API wrapper. - * + * * @author Daniil Gentili */ class Graph diff --git a/src/PluginGraph/GraphInternal.php b/src/PluginGraph/GraphInternal.php index 6eec819..793db50 100644 --- a/src/PluginGraph/GraphInternal.php +++ b/src/PluginGraph/GraphInternal.php @@ -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); diff --git a/src/PluginGraph/Node.php b/src/PluginGraph/Node.php index 9ac76c8..96616d1 100644 --- a/src/PluginGraph/Node.php +++ b/src/PluginGraph/Node.php @@ -9,7 +9,7 @@ use SplQueue; /** * Represents a plugin with a certain configuration. - * + * * @author Daniil Gentili */ 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); + } } diff --git a/src/PluginGraph/PackageContext.php b/src/PluginGraph/PackageContext.php index a059892..3761978 100644 --- a/src/PluginGraph/PackageContext.php +++ b/src/PluginGraph/PackageContext.php @@ -4,7 +4,7 @@ namespace Phabel\PluginGraph; /** * List of packages associated with plugin. - * + * * @author Daniil Gentili */ 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 diff --git a/src/PluginGraph/Plugins.php b/src/PluginGraph/Plugins.php index 07f81b8..4dfd78f 100644 --- a/src/PluginGraph/Plugins.php +++ b/src/PluginGraph/Plugins.php @@ -7,7 +7,7 @@ use SplQueue; /** * Representation of multiple plugins+configs. - * + * * @author Daniil Gentili */ class Plugins diff --git a/src/PluginInterface.php b/src/PluginInterface.php index a1cfe61..ddb96f0 100644 --- a/src/PluginInterface.php +++ b/src/PluginInterface.php @@ -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; diff --git a/src/Target/Php70.php b/src/Target/Php70.php index e531402..1eb7093 100644 --- a/src/Target/Php70.php +++ b/src/Target/Php70.php @@ -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 + * @license MIT */ class Php70 extends Plugin { + public static function composerRequires(): array + { + return ['symfony/polyfill-php70' => '*']; + } public static function runWithAfter(): array { return [ diff --git a/src/Target/Php70/AnonymousClassReplacer.php b/src/Target/Php70/AnonymousClassReplacer.php index 240915f..8fa6da4 100644 --- a/src/Target/Php70/AnonymousClassReplacer.php +++ b/src/Target/Php70/AnonymousClassReplacer.php @@ -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 { diff --git a/src/Target/Php71.php b/src/Target/Php71.php index 585e465..40d5884 100644 --- a/src/Target/Php71.php +++ b/src/Target/Php71.php @@ -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 + * @license MIT */ class Php71 extends Plugin { + public static function composerRequires(): array + { + return ['symfony/polyfill-php70' => '*']; + } public static function runWithAfter(): array { return [ diff --git a/src/Target/Php72.php b/src/Target/Php72.php new file mode 100644 index 0000000..22999af --- /dev/null +++ b/src/Target/Php72.php @@ -0,0 +1,19 @@ + + * @license MIT + */ +class Php72 extends Plugin +{ + public static function composerRequires(): array + { + return ['symfony/polyfill-php72' => '*']; + } +} diff --git a/src/Target/Php73.php b/src/Target/Php73.php new file mode 100644 index 0000000..fcd973a --- /dev/null +++ b/src/Target/Php73.php @@ -0,0 +1,19 @@ + + * @license MIT + */ +class Php73 extends Plugin +{ + public static function composerRequires(): array + { + return ['symfony/polyfill-php72' => '*']; + } +} diff --git a/src/Target/Php74.php b/src/Target/Php74.php new file mode 100644 index 0000000..683131c --- /dev/null +++ b/src/Target/Php74.php @@ -0,0 +1,19 @@ + + * @license MIT + */ +class Php74 extends Plugin +{ + public static function composerRequires(): array + { + return ['symfony/polyfill-php72' => '*']; + } +} diff --git a/src/Traverser.php b/src/Traverser.php index 15d1102..ff0ad34 100644 --- a/src/Traverser.php +++ b/src/Traverser.php @@ -2,9 +2,167 @@ namespace Phabel; +use PhpParser\Node; +use PhpParser\Parser; +use PhpParser\ParserFactory; +use SplQueue; + /** * @author Daniil Gentili */ class Traverser { + /** + * Plugin queue. + * + * @return SplQueue> + */ + private SplQueue $queue; + /** + * Parser instance. + */ + private Parser $parser; + /** + * Plugin queue for specific package. + * + * @return SplQueue> + */ + private SplQueue $packageQueue; + /** + * AST traverser. + * + * @return SplQueue> $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> */ + $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 $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 $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; + } + } + } + } + } } diff --git a/test/dump.php b/test/dump.php index a5381af..f588d9e 100644 --- a/test/dump.php +++ b/test/dump.php @@ -11,4 +11,4 @@ if ($argc < 2) { $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); -var_dump($parser->parse(file_get_contents($argv[1]))); \ No newline at end of file +\var_dump($parser->parse(\file_get_contents($argv[1])));