Add initial repository

This commit is contained in:
Daniil Gentili 2020-08-13 18:30:12 +02:00
parent 3cd8d11ebf
commit f1d8722889
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
19 changed files with 416 additions and 91 deletions

View File

@ -3,11 +3,14 @@
"description": "Write and deploy modern PHP 8 code, today.",
"type": "project",
"require": {
"nikic/php-parser": "^4.7"
"nikic/php-parser": "^4.7",
"composer-plugin-api": "^1|^2",
"amphp/byte-stream": "^1.7"
},
"require-dev": {
"phpunit/phpunit": "^7 | ^8 | ^9",
"amphp/php-cs-fixer-config": "dev-master"
"amphp/php-cs-fixer-config": "dev-master",
"composer/composer": "^1|^2"
},
"license": "MIT",
"authors": [{
@ -23,4 +26,4 @@
"cs-fix": "php-cs-fixer fix -v --diff",
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text"
}
}
}

62
src/Composer/Plugin.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace Phabel\Composer;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Plugin implements PluginInterface, EventSubscriberInterface
{
/**
* IO interface.
*/
private IOInterface $io;
/**
* Apply plugin modifications to Composer.
*
* @param Composer $composer Composer instance
* @param IOInterface $io IO instance
*
* @return void
*/
public function activate(Composer $composer, IOInterface $io): void
{
$repoManager = $composer->getRepositoryManager();
$repos = $repoManager->getRepositories();
$repoManager->prependRepository()
$this->io = $io;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
InstallerEvents::PRE_DEPENDENCIES_SOLVING =>
['onDependencySolve', 100000],
];
}
/**
* Emitted before composer solves dependencies
*
* @param InstallerEvent $event Event
*
* @return void
*/
public function onDependencySolve(InstallerEvent $event): void
{
var_dump($event);
}
}

225
src/Composer/Repository.php Normal file
View File

@ -0,0 +1,225 @@
<?php
namespace Phabel\Composer;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\ComposerRepository;
use Composer\Semver\Constraint\ConstraintInterface;
class Repository extends ComposerRepository
{
/**
* Configuration prefix.
*/
private const CONFIG_PREFIX = 'phabel-config';
/**
* Previous repository .
*/
private ComposerRepository $repository;
/**
* Constructor.
*
* @param RepositoryInterface[] $repositories Previous repositories
*/
public function __construct(ComposerRepository $repository)
{
$this->repository = $repository;
}
/**
* Get repository configuration.
*
* @return mixed
*/
public function getRepoConfig()
{
return $this->repository->getRepoConfig();
}
/**
* Set root aliases.
*
* @param array $rootAliases Root aliases
*
* @return void
*/
public function setRootAliases(array $rootAliases): void
{
$this->repository->setRootAliases($rootAliases);
}
/**
* Checks if specified package registered (installed).
*
* @param PackageInterface $package package instance
*
* @return bool
*/
public function hasPackage(PackageInterface $package): bool
{
return $this->repository->hasPackage($package);
}
/**
* Look for phabel configuration parameters in constraint.
*
* @param string|\Composer\Semver\Constraint\ConstraintInterface|null &$constraint package version or version constraint to match against
*
* @return array
*/
private static function prepareConstraint(&$constraint): array
{
if (!$constraint instanceof ConstraintInterface && !\is_string($constraint)) {
return [];
}
$constraint = (string) $constraint;
if (!str_starts_with($constraint, self::CONFIG_PREFIX)) {
return [];
}
[$config, $constraint] = \explode("\n", $constraint, 2);
return \json_decode(\substr($config, 0, \strlen(self::CONFIG_PREFIX)), true) ?: [];
}
/**
* Prepare package.
*
* @param PackageInterface $package Package
* @param array $config Configuration inherited from constraint
*
* @return void
*/
private static function preparePackage(PackageInterface $package, array $config): void
{
/**
* Phabel configuration of current package.
* @var array
*/
$myConfig = $package->getExtra()['phabel'] ?? [];
$havePhabel = false;
$myConfig['target'] ??= '7.0';
/** @var array */
foreach ($package->getRequires() as $link) {
if ($link->getTarget() === 'phabel/phabel') {
$havePhabel = true;
}
if ($link->getTarget() === 'php') {
$myConfig['target'] = $link->getConstraint();
if ($havePhabel) {
break;
}
}
}
if (!$havePhabel) {
return;
}
// Config merging logic here...
$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());
}
$package->setRequires($links);
}
/**
* Searches for the first match of a package by name and version.
*
* @param string $name package name
* @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
*
* @return PackageInterface|null
*/
public function findPackage(string $name, $constraint): ?PackageInterface
{
$config = self::prepareConstraint($constraint);
if (!$package = $this->repository->findPackage($name, $constraint)) {
return null;
}
return self::preparePackage($package, $config);
}
/**
* Searches for all packages matching a name and optionally a version.
*
* @param string $name package name
* @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
*
* @return PackageInterface[]
*/
public function findPackages($name, $constraint = null)
{
$config = self::prepareConstraint($constraint);
foreach ($packages = $this->repository->findPackages($name, $constraint) as $package) {
self::preparePackage($package, $config);
}
return $packages;
}
/**
* Returns list of registered packages.
*
* @return PackageInterface[]
*/
public function getPackages()
{
$packages = $this->repository->getPackages();
foreach ($packages as $package) {
self::preparePackage($package, []);
}
return $packages;
}
/**
* {@inheritDoc}
*/
public function search($query, $mode = 0, $type = null)
{
return $this->repository->search($query, $mode, $type);
}
public function getProviderNames()
{
return $this->repository->getProviderNames();
}
public function hasProviders()
{
return $this->repository->hasProviders();
}
public function resetPackageIds()
{
return $this->repository->resetPackageIds();
}
public function addPackage(PackageInterface $package)
{
$this->repository->addPackage($package);
}
/**
* @param Pool $pool
* @param string $name package name
* @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache
* @return array|mixed
*/
public function whatProvides(Pool $pool, $name, $bypassFilters = false)
{
$whatProvides = $this->repository->whatProvides($pool, $name, $bypassFilters);
foreach ($whatProvides as $package => &$versions) {
foreach ($versions as &$version) {
if (!isset($version['require']['phabel/phabel'])) {
continue;
}
$config = $version['extra']['phabel'] ?? [];
if (!isset($config['target'])) {
if (isset($version['require']['php'])) {
$config['target'] = $version['require']['php'];
} else {
$config['target'] = '7.0';
}
}
foreach ($version['require'] as $package => &$version) {
$version = self::CONFIG_PREFIX.\json_encode($config)."\n".$version;
}
}
}
return $whatProvides;
}
}

View File

@ -2,6 +2,9 @@
namespace Phabel;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
class Context
{
}

View File

@ -15,6 +15,9 @@ use PhpParser\Node\Stmt\Expression;
use PhpParser\ParserFactory;
use ReflectionClass;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class Plugin implements PluginInterface
{
/**
@ -31,7 +34,7 @@ abstract class Plugin implements PluginInterface
* @param array $config
* @return void
*/
public function setConfigArray(array $config)
public function setConfigArray(array $config): void
{
$this->config = $config;
}
@ -207,6 +210,13 @@ abstract class Plugin implements PluginInterface
{
return $configs;
}
/**
* {@inheritDoc}
*/
public static function splitConfig(array $config): array
{
return [$config];
}
/**
* {@inheritDoc}
*/

View File

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

View File

@ -11,6 +11,8 @@ 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

@ -4,6 +4,8 @@ namespace Phabel;
/**
* Caches plugin information.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class PluginCache
{

View File

@ -4,6 +4,9 @@ namespace Phabel\PluginGraph;
use Phabel\PluginInterface;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
class CircularException extends \Exception
{
/**

View File

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

View File

@ -6,6 +6,9 @@ use Phabel\PluginInterface;
use SplObjectStorage;
use SplQueue;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
class GraphInternal
{
/**
@ -59,12 +62,7 @@ class GraphInternal
*/
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 \array_map(fn (array $config): Node => $this->addPluginInternal($plugin, $config, $ctx), $plugin::splitConfig($config));
}
/**
* Add plugin.

View File

@ -9,6 +9,8 @@ use SplQueue;
/**
* Represents a plugin with a certain configuration.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class Node
{

View File

@ -4,6 +4,8 @@ namespace Phabel\PluginGraph;
/**
* List of packages associated with plugin.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class PackageContext
{

View File

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

View File

@ -4,6 +4,9 @@ namespace Phabel;
use Phabel\PluginGraph\PackageContext;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
interface PluginInterface
{
/**

40
src/Target/Php70.php Normal file
View File

@ -0,0 +1,40 @@
<?php
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;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
class Php70 extends Plugin
{
public static function runWithAfter(): array
{
return [
AnonymousClassReplacer::class,
ClosureCallReplacer::class,
CompoundAccess::class,
DefineArrayReplacer::class,
GroupUseReplacer::class,
NullCoalesceReplacer::class,
ReservedNameReplacer::class,
ScalarTypeHintsRemover::class,
SpaceshipOperatorReplacer::class,
StrictTypesDeclareStatementRemover::class,
ThrowableReplacer::class
];
}
}

View File

@ -1,16 +1,16 @@
<?php
namespace Spatie\Php7to5\NodeVisitors;
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 PhpParser\NodeVisitorAbstract;
use Spatie\Php7to5\Converter;
use Spatie\Php7to5\Exceptions\InvalidPhpCode;
class AnonymousClassReplacer extends NodeVisitorAbstract
class AnonymousClassReplacer extends Plugin
{
/**
* @var array
@ -47,82 +47,4 @@ class AnonymousClassReplacer extends NodeVisitorAbstract
return $newNode;
}
/**
* {@inheritdoc}
*/
public function afterTraverse(array $nodes)
{
if (\count($this->anonymousClassNodes) === 0) {
return $nodes;
}
$anonymousClassStatements = $this->anonymousClassNodes;
$anonymousClassStatements = $this->convertToPhp5Statements($anonymousClassStatements);
$hookIndex = $this->getAnonymousClassHookIndex($nodes);
$nodes = $this->moveAnonymousClassesToHook($nodes, $hookIndex, $anonymousClassStatements);
return $nodes;
}
/**
* Find the index of the first statement that is not a declare, use or namespace statement.
*
* @param array $statements
*
* @return int
*
* @throws \Spatie\Php7to5\Exceptions\InvalidPhpCode
*/
protected function getAnonymousClassHookIndex(array $statements)
{
$hookIndex = false;
foreach ($statements as $index => $statement) {
if (!$statement instanceof Declare_ &&
!$statement instanceof Use_ &&
!$statement instanceof Namespace_) {
$hookIndex = $index;
}
}
if ($hookIndex === false) {
return 1;
//throw InvalidPhpCode::noValidLocationFoundToInsertClasses();
}
return $hookIndex;
}
/**
* @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);
return \array_merge($preStatements, $anonymousClassStatements, $postStatements);
}
/**
* @param array $php7statements
*
* @return \PhpParser\Node[]
*/
public function convertToPhp5Statements(array $php7statements)
{
$converter = Converter::getTraverser($php7statements);
$php5Statements = $converter->traverse($php7statements);
return $php5Statements;
}
}

39
src/Target/Php71.php Normal file
View File

@ -0,0 +1,39 @@
<?php
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;
use Phabel\Target\Php71\MultipleCatchReplacer;
use Spatie\Php7to5\NodeVisitors\NullableTypeRemover;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
class Php71 extends Plugin
{
public static function runWithAfter(): array
{
return [
ArrayList::class,
ClassConstantVisibilityModifiersRemover::class,
ListKey::class,
MultipleCatchReplacer::class,
NullableTypeRemover::class
];
}
}

View File

@ -2,6 +2,9 @@
namespace Phabel;
/**
* @author Daniil Gentili <daniil@daniil.it>
*/
class Traverser
{
}