From 409c6c2f1f63e1727e4f7658ca59f76acdb5627f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 1 Nov 2020 20:44:11 +0100 Subject: [PATCH] Start finalizing composer integration --- src/Composer/Plugin.php | 10 ++- src/Composer/Repository.php | 14 ++--- src/Context.php | 23 +++++++ src/PluginCache.php | 35 +++++++---- src/PluginGraph/GraphInternal.php | 8 ++- src/PluginGraph/Node.php | 16 ++--- src/Target/Php.php | 70 +++++++++++++++++++++ src/Target/Php55.php | 26 -------- src/Target/Php70.php | 52 --------------- src/Target/Php71.php | 42 ------------- src/Target/Php71/ListExpression.php | 1 + src/Target/Php72.php | 31 --------- src/Target/Php73.php | 28 --------- src/Target/Php74.php | 36 ----------- src/Target/Php74/NullCoalesceAssignment.php | 5 ++ src/Target/Php74/Serializable.php | 49 +++++++++++++++ src/Target/Php74/TypedProperty.php | 24 ++++++- src/Target/Php80.php | 31 --------- src/Traverser.php | 1 + 19 files changed, 221 insertions(+), 281 deletions(-) create mode 100644 src/Target/Php.php delete mode 100644 src/Target/Php55.php delete mode 100644 src/Target/Php70.php delete mode 100644 src/Target/Php71.php delete mode 100644 src/Target/Php72.php delete mode 100644 src/Target/Php73.php delete mode 100644 src/Target/Php74.php create mode 100644 src/Target/Php74/Serializable.php delete mode 100644 src/Target/Php80.php diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php index c8ae481..eab1f4d 100644 --- a/src/Composer/Plugin.php +++ b/src/Composer/Plugin.php @@ -6,6 +6,8 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer\InstallerEvent; use Composer\Installer\InstallerEvents; +use Composer\Installer\PackageEvent; +use Composer\Installer\PackageEvents; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; @@ -31,7 +33,7 @@ class Plugin implements PluginInterface, EventSubscriberInterface { $repoManager = $composer->getRepositoryManager(); $repos = $repoManager->getRepositories(); - $repoManager->prependRepository(); + $repoManager->prependRepository(new Repository($repos[0])); $this->io = $io; } @@ -43,9 +45,15 @@ class Plugin implements PluginInterface, EventSubscriberInterface return [ InstallerEvents::PRE_DEPENDENCIES_SOLVING => ['onDependencySolve', 100000], + PackageEvents::POST_PACKAGE_INSTALL => + ['onInstall', 100000], ]; } + public function onInstall(PackageEvent $event): void + { + var_dumP($event); + } /** * Emitted before composer solves dependencies. diff --git a/src/Composer/Repository.php b/src/Composer/Repository.php index ee1a25d..acfeea5 100644 --- a/src/Composer/Repository.php +++ b/src/Composer/Repository.php @@ -2,9 +2,11 @@ namespace Phabel\Composer; +use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\ComposerRepository; +use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; /** @@ -100,8 +102,6 @@ class Repository extends ComposerRepository */ $myConfig = $package->getExtra()['phabel'] ?? []; $havePhabel = false; - $myConfig['target'] ??= '7.0'; - /** @var array */ foreach ($package->getRequires() as $link) { if ($link->getTarget() === 'phabel/phabel') { $havePhabel = true; @@ -120,7 +120,7 @@ class Repository extends ComposerRepository $links = []; foreach ($package->getRequires() as $link) { $version = self::CONFIG_PREFIX.\json_encode($config)."\n".($link->getConstraint() ?? ''); - $links []= new Link($link->getSource(), $link->getTarget(), $version, $link->getDescription()); + $links []= new Link($link->getSource(), $link->getTarget(), new Constraint('>=', $version), $link->getDescription()); } $package->setRequires($links); } @@ -213,12 +213,8 @@ class Repository extends ComposerRepository continue; } $config = $version['extra']['phabel'] ?? []; - if (!isset($config['target'])) { - if (isset($version['require']['php'])) { - $config['target'] = $version['require']['php']; - } else { - $config['target'] = '7.0'; - } + if (!isset($config['target']) && isset($version['require']['php'])) { + $config['target'] = $version['require']['php']; } foreach ($version['require'] as $package => &$version) { $version = self::CONFIG_PREFIX.\json_encode($config)."\n".$version; diff --git a/src/Context.php b/src/Context.php index 9ab4e46..75e0946 100644 --- a/src/Context.php +++ b/src/Context.php @@ -3,6 +3,8 @@ namespace Phabel; use PhpParser\BuilderHelpers; +use PhpParser\ErrorHandler\Throwing; +use PhpParser\NameContext; use PhpParser\Node; use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\ArrowFunction; @@ -23,6 +25,7 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; use PhpParser\Node\Param; use PhpParser\Node\Stmt\If_; +use PhpParser\NodeVisitor\NameResolver; use SplStack; /** @@ -45,6 +48,12 @@ class Context * @var SplStack */ public SplStack $variables; + /** + * Name resolver. + * + * @var NameResolver + */ + public NameResolver $nameResolver; /** * Constructor. */ @@ -54,6 +63,8 @@ class Context $this->parents = new SplStack; /** @var SplStack */ $this->variables = new SplStack; + $this->nameResolver = new NameResolver(new Throwing, ['replaceNodes' => false]); + $this->nameResolver->beforeTraverse([]); } /** * Push node. @@ -67,6 +78,8 @@ class Context $this->parents->push($node); if ($node instanceof RootNode) { $this->variables->push(new VariableContext); + } else { + $this->nameResolver->enterNode($node); } if ($node instanceof FunctionLike) { $variables = \array_fill_keys( @@ -293,4 +306,14 @@ class Context $subNodeIndex = $parent->getAttribute('currentNodeIndex'); \array_splice($parent->{$subNode}, $subNodeIndex+1, 0, $nodes); } + + /** + * Gets name context + * + * @return NameContext + */ + public function getNameContext(): NameContext + { + return $this->nameResolver->getNameContext(); + } } diff --git a/src/PluginCache.php b/src/PluginCache.php index 2965185..60f8d73 100644 --- a/src/PluginCache.php +++ b/src/PluginCache.php @@ -92,72 +92,81 @@ class PluginCache * Get runBefore requirements. * * @param class-string $plugin Plugin + * @param array $config Config * - * @return array, array> + * @return array + * @psalm-return array, array> */ - public static function runBefore(string $plugin): array + public static function runBefore(string $plugin, array $config): array { /** @var array, array, array>> */ static $cache = []; if (isset($cache[$plugin])) { return $cache[$plugin]; } - return $cache[$plugin] = self::simplify($plugin::runBefore()); + return $cache[$plugin] = self::simplify($plugin::runBefore($config)); } /** * Get runAfter requirements. * * @param class-string $plugin Plugin + * @param array $config Config * - * @return array, array> + * @return array + * @psalm-return array, array> */ - public static function runAfter(string $plugin): array + public static function runAfter(string $plugin, array $config): array { /** @var array, array, array>> */ static $cache = []; if (isset($cache[$plugin])) { return $cache[$plugin]; } - return $cache[$plugin] = self::simplify($plugin::runAfter()); + return $cache[$plugin] = self::simplify($plugin::runAfter($config)); } /** * Get runWithBefore requirements. * * @param class-string $plugin Plugin + * @param array $config Config * - * @return array, array> + * @return array + * @psalm-return array, array> */ - public static function runWithBefore(string $plugin): array + public static function runWithBefore(string $plugin, array $config): array { /** @var array, array, array>> */ static $cache = []; if (isset($cache[$plugin])) { return $cache[$plugin]; } - return $cache[$plugin] = self::simplify($plugin::runWithBefore()); + return $cache[$plugin] = self::simplify($plugin::runWithBefore($config)); } /** * Get runWithAfter requirements. * * @param class-string $plugin Plugin + * @param array $config Config * - * @return array, array> + * @return array + * @psalm-return array, array> */ - public static function runWithAfter(string $plugin): array + public static function runWithAfter(string $plugin, array $config): array { /** @var array, array, array>> */ static $cache = []; if (isset($cache[$plugin])) { return $cache[$plugin]; } - return $cache[$plugin] = self::simplify($plugin::runWithAfter()); + return $cache[$plugin] = self::simplify($plugin::runWithAfter($config)); } /** * Simplify requirements. * * @param (array, array>|class-string[]) $requirements Requirements * - * @return array, array> + * @return array + * @psalm-return array, array> */ private static function simplify(array $requirements): array { diff --git a/src/PluginGraph/GraphInternal.php b/src/PluginGraph/GraphInternal.php index a8a17b2..fcba23c 100644 --- a/src/PluginGraph/GraphInternal.php +++ b/src/PluginGraph/GraphInternal.php @@ -57,10 +57,12 @@ class GraphInternal /** * Add plugin. * - * @param class-string $plugin Plugin to add - * @param array $config Plugin configuration - * @param PackageContext $ctx Package context + * @param string $plugin Plugin to add + * @param array $config Plugin configuration + * @param PackageContext $ctx Package context * + * @psalm-param class-string $plugin Plugin to add + * * @return Node[] */ public function addPlugin(string $plugin, array $config, PackageContext $ctx): array diff --git a/src/PluginGraph/Node.php b/src/PluginGraph/Node.php index 406e10b..7c68e6d 100644 --- a/src/PluginGraph/Node.php +++ b/src/PluginGraph/Node.php @@ -38,28 +38,28 @@ class Node /** * Nodes that this node requires. * - * @var SplObjectStorage + * @var SplObjectStorage */ private SplObjectStorage $requires; /** * Nodes that this node extends. * - * @var SplObjectStorage + * @var SplObjectStorage */ private SplObjectStorage $extends; /** * Nodes that require this node. * - * @var SplObjectStorage + * @var SplObjectStorage */ private SplObjectStorage $requiredBy; /** * Nodes that extend this node. * - * @var SplObjectStorage + * @var SplObjectStorage */ private SplObjectStorage $extendedBy; @@ -109,22 +109,22 @@ class Node $this->plugin = new Plugins($plugin, $config); $this->canBeRequired = PluginCache::canBeRequired($plugin); - foreach (PluginCache::runAfter($plugin) as $class => $config) { + foreach (PluginCache::runAfter($plugin, $config) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $this->packageContext) as $node) { $this->require($node); } } - foreach (PluginCache::runBefore($plugin) as $class => $config) { + foreach (PluginCache::runBefore($plugin, $config) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $this->packageContext) as $node) { $node->require($this); } } - foreach (PluginCache::runWithAfter($plugin) as $class => $config) { + foreach (PluginCache::runWithAfter($plugin, $config) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $this->packageContext) as $node) { $this->extend($node); } } - foreach (PluginCache::runWithBefore($plugin) as $class => $config) { + foreach (PluginCache::runWithBefore($plugin, $config) as $class => $config) { foreach ($this->graph->addPlugin($class, $config, $this->packageContext) as $node) { $node->extend($this); } diff --git a/src/Target/Php.php b/src/Target/Php.php new file mode 100644 index 0000000..ef4bb0f --- /dev/null +++ b/src/Target/Php.php @@ -0,0 +1,70 @@ + + * @license MIT + */ +class Php extends Plugin +{ + /** + * PHP versions. + */ + private const VERSIONS = [ + '55', + '56', + '70', + '71', + '72', + '73', + '74', + '80', + ]; + /** + * Default target. + */ + private const DEFAULT_TARGET = '70'; + /** + * Get PHP version range to target. + * + * @param array $config + * @return array + */ + private static function getRange(array $config): array + { + $target = $config['target'] ?? PHP_MAJOR_VERSION.PHP_MINOR_VERSION; + if (preg_match(":^\D*(\d+\.\d+)\..*:", $config['target'], $matches)) { + $target = $matches[1]; + } + $key = \array_search(str_replace('.', '', $target), self::VERSIONS); + return \array_slice( + self::VERSIONS, + $key === false ? self::DEFAULT_TARGET : $key + ); + } + public static function composerRequires(array $config): array + { + return \array_fill_keys( + \array_map(fn (string $version): string => "symfony/polyfill-$version", self::getRange($config)), + '*' + ); + } + public static function runWithAfter(array $config): array + { + $classes = []; + foreach (self::getRange($config) as $version) { + foreach (scandir(__DIR__."/Php$version") as $file) { + if (substr($file, -4) !== '.php') continue; + $class = basename($version, '.php'); + $classes[$class] = $config[$class] ?? []; + } + } + return $classes; + } +} diff --git a/src/Target/Php55.php b/src/Target/Php55.php deleted file mode 100644 index 41a3b01..0000000 --- a/src/Target/Php55.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @license MIT - */ -class Php55 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php55' => '*']; - } - public static function runWithAfter(): array - { - return [ - YieldDetector::class - ]; - } -} diff --git a/src/Target/Php70.php b/src/Target/Php70.php deleted file mode 100644 index c2401f9..0000000 --- a/src/Target/Php70.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @license MIT - */ -class Php70 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php70' => '*']; - } - public static function runWithAfter(): array - { - return [ - IssetExpressionFixer::class, - NestedExpressionFixer::class, - AnonymousClassReplacer::class, - ClosureCallReplacer::class, - CompoundAccess::class, - DefineArrayReplacer::class, - GroupUseReplacer::class, - NullCoalesceReplacer::class, - ReservedNameReplacer::class, - ScalarTypeHints::class, - SpaceshipOperatorReplacer::class, - StrictTypesDeclareStatementRemover::class, - ThrowableReplacer::class, - YieldFromReturnDetector::class - ]; - } -} diff --git a/src/Target/Php71.php b/src/Target/Php71.php deleted file mode 100644 index ecfd4a8..0000000 --- a/src/Target/Php71.php +++ /dev/null @@ -1,42 +0,0 @@ - - * @license MIT - */ -class Php71 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php71' => '*']; - } - public static function runWithAfter(): array - { - return [ - IssetExpressionFixer::class, - NestedExpressionFixer::class, - ArrayList::class, - ClassConstantVisibilityModifiersRemover::class, - ListKey::class, - IterableHint::class, - MultipleCatchReplacer::class, - VoidReturnType::class, - NullableType::class - ]; - } -} diff --git a/src/Target/Php71/ListExpression.php b/src/Target/Php71/ListExpression.php index 7332806..e06b201 100644 --- a/src/Target/Php71/ListExpression.php +++ b/src/Target/Php71/ListExpression.php @@ -4,6 +4,7 @@ namespace Phabel\Target\Php71; use Phabel\Context; use Phabel\Plugin; +use Phabel\Target\Php73\ListReference; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\List_; diff --git a/src/Target/Php72.php b/src/Target/Php72.php deleted file mode 100644 index dbcfe3c..0000000 --- a/src/Target/Php72.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @license MIT - */ -class Php72 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php72' => '*']; - } - - public static function runWithAfter(): array - { - return [ - IssetExpressionFixer::class, - NestedExpressionFixer::class, - ObjectTypeHintReplacer::class - ]; - } -} diff --git a/src/Target/Php73.php b/src/Target/Php73.php deleted file mode 100644 index 241ccec..0000000 --- a/src/Target/Php73.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @license MIT - */ -class Php73 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php73' => '*']; - } - public static function runWithAfter(): array - { - return [ - IssetExpressionFixer::class, - NestedExpressionFixer::class, - ]; - } -} diff --git a/src/Target/Php74.php b/src/Target/Php74.php deleted file mode 100644 index a6e7dc0..0000000 --- a/src/Target/Php74.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @license MIT - */ -class Php74 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php74' => '*']; - } - public static function runWithAfter(): array - { - return [ - IssetExpressionFixer::class, - NestedExpressionFixer::class, - ArrayUnpack::class, - ArrowClosure::class, - NullCoalesceAssignment::class, - TypedProperty::class - ]; - } -} diff --git a/src/Target/Php74/NullCoalesceAssignment.php b/src/Target/Php74/NullCoalesceAssignment.php index 2dbb441..cd5243e 100644 --- a/src/Target/Php74/NullCoalesceAssignment.php +++ b/src/Target/Php74/NullCoalesceAssignment.php @@ -3,6 +3,7 @@ namespace Phabel\Target\Php74; use Phabel\Plugin; +use Phabel\Target\Php70\NullCoalesceReplacer; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignOp\Coalesce; use PhpParser\Node\Expr\BinaryOp\Coalesce as BinaryOpCoalesce; @@ -17,4 +18,8 @@ class NullCoalesceAssignment extends Plugin { return new Assign($coalesce->var, new BinaryOpCoalesce($coalesce->var, $coalesce->expr), $coalesce->getAttributes()); } + public static function runWithBefore(): array + { + return [NullCoalesceReplacer::class]; + } } diff --git a/src/Target/Php74/Serializable.php b/src/Target/Php74/Serializable.php new file mode 100644 index 0000000..1c77910 --- /dev/null +++ b/src/Target/Php74/Serializable.php @@ -0,0 +1,49 @@ + */ + $methods = []; + foreach ($class->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + $name = $stmt->name->toLowerString(); + $methods[$name] = $stmt; + } + } + + if (!isset($methods['__serialize']) && !isset($methods['__unserialize'])) { + return; + } + foreach ($class->implements as $name) { + $resolved = $context->getNameContext()->getResolvedClassName($name); + if ($resolved->toLowerString() === 'serializable' || $name->toLowerString() === 'serializable') { + return; // Already implements + } + } + if (isset($methods['__sleep'])) { + $methods['__sleep']->name = new Identifier('__phabelSleep'); + } + if (isset($methods['__wakeup'])) { + $methods['__wakeup']->name = new Identifier('__phabelWakeup'); + } + + $class->implements []= new FullyQualified(GlobalSerializable::class); + $methods['serialize'] = new ClassM + } +} diff --git a/src/Target/Php74/TypedProperty.php b/src/Target/Php74/TypedProperty.php index fac0369..2a1d750 100644 --- a/src/Target/Php74/TypedProperty.php +++ b/src/Target/Php74/TypedProperty.php @@ -2,11 +2,33 @@ namespace Phabel\Target\Php74; +use Phabel\Context; use Phabel\Plugin; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Property; /** - * + * Implement typed properties. */ class TypedProperty extends Plugin { + public function enter(Class_ $class, Context $context): void + { + /** @var Property[] */ + $typed = []; + foreach ($class->stmts as $stmt) { + if ($stmt instanceof Property && $stmt->type) { + $typed []= $stmt; + } + } + + if (empty($typed)) { + return; + } + + if + foreach ($typed as $property) { + + } + } } diff --git a/src/Target/Php80.php b/src/Target/Php80.php deleted file mode 100644 index 8952f3e..0000000 --- a/src/Target/Php80.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @license MIT - */ -class Php80 extends Plugin -{ - public static function composerRequires(): array - { - return ['symfony/polyfill-php80' => '*']; - } - - public static function runWithAfter(): array - { - return [ - IssetExpressionFixer::class, - NestedExpressionFixer::class, - UnionTypeStripper::class, - ]; - } -} diff --git a/src/Traverser.php b/src/Traverser.php index 87fdbe6..6113056 100644 --- a/src/Traverser.php +++ b/src/Traverser.php @@ -3,6 +3,7 @@ namespace Phabel; use PhpParser\Node; +use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\ParserFactory; use SplQueue;