Initial commit
This commit is contained in:
commit
c43eac2b19
|
@ -0,0 +1,2 @@
|
||||||
|
/vendor/
|
||||||
|
composer.lock
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "phabel/phabel",
|
||||||
|
"description": "Write and deploy modern PHP 8 code, today.",
|
||||||
|
"type": "project",
|
||||||
|
"require": {
|
||||||
|
"nikic/php-parser": "^4.7"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniil Gentili",
|
||||||
|
"email": "daniil@daniil.it"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
errorLevel="1"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="src" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
</psalm>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel;
|
||||||
|
|
||||||
|
class Context
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Context;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
|
||||||
|
class UniqueId extends Plugin
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Expr\FuncCall;
|
||||||
|
use PhpParser\Node\Expr\StaticCall;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Stmt\Expression;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
abstract class Plugin implements PluginInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Configuration array.
|
||||||
|
*/
|
||||||
|
private array $config = [];
|
||||||
|
/**
|
||||||
|
* Replace node of one type with another.
|
||||||
|
*
|
||||||
|
* @param Node $node Original node
|
||||||
|
* @param string $class Class of new node
|
||||||
|
* @param array $propertyMap Property map between old and new objects
|
||||||
|
*
|
||||||
|
* @psalm-param class-string<Node> $class Class of new node
|
||||||
|
* @psalm-param array<string, string> $propertyMap Property map between old and new objects
|
||||||
|
*
|
||||||
|
* @return Node
|
||||||
|
*/
|
||||||
|
public static function replaceType(Node $node, string $class, array $propertyMap = []): Node
|
||||||
|
{
|
||||||
|
if ($propertyMap) {
|
||||||
|
/** @var Node */
|
||||||
|
$nodeNew = (new ReflectionClass($class))->newInstanceWithoutConstructor();
|
||||||
|
foreach ($propertyMap as $old => $new) {
|
||||||
|
$nodeNew->{$new} = $node->{$old};
|
||||||
|
}
|
||||||
|
$nodeNew->setAttributes($node->getAttributes());
|
||||||
|
return $nodeNew;
|
||||||
|
}
|
||||||
|
return new $class(
|
||||||
|
...\array_map(fn (string $name) => $node->{$name}, $node->getSubNodeNames()),
|
||||||
|
$node->getAttributes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Replace type in-place.
|
||||||
|
*
|
||||||
|
* @param Node &$node Original node
|
||||||
|
* @param string $class Class of new node
|
||||||
|
* @param array $propertyMap Property map between old and new objects
|
||||||
|
*
|
||||||
|
* @psalm-param class-string<Node> $class Class of new node
|
||||||
|
* @psalm-param array<string, string> $propertyMap Property map between old and new objects
|
||||||
|
*
|
||||||
|
* @param-out Node &$node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function replaceTypeInPlace(Node &$node, string $class, array $propertyMap = []): void
|
||||||
|
{
|
||||||
|
$node = self::replaceType($node, $class, $propertyMap);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Call function.
|
||||||
|
*
|
||||||
|
* @param class-string|array{0: class-string, 1: string} $name Function name
|
||||||
|
* @param Expr|Arg ...$parameters Parameters
|
||||||
|
*
|
||||||
|
* @return FuncCall|StaticCall
|
||||||
|
*/
|
||||||
|
public static function call($name, ...$parameters)
|
||||||
|
{
|
||||||
|
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
|
||||||
|
return \is_array($name) ? new StaticCall(new Name($name[0]), $name[1], $parameters) : new FuncCall(new Name($name), $parameters);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Call polyfill function from current plugin.
|
||||||
|
*
|
||||||
|
* @param string $name Function name
|
||||||
|
* @param Expr|Arg ...$parameters Parameters
|
||||||
|
*
|
||||||
|
* @return StaticCall
|
||||||
|
*/
|
||||||
|
protected static function callPoly(string $name, ...$parameters): StaticCall
|
||||||
|
{
|
||||||
|
return self::call([static::class, $name], ...$parameters);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert array, int or other literal to node.
|
||||||
|
*
|
||||||
|
* @param mixed $data Data to convert
|
||||||
|
*
|
||||||
|
* @return Node
|
||||||
|
*/
|
||||||
|
public static function toLiteral($data): Node
|
||||||
|
{
|
||||||
|
/** @var Node[] */
|
||||||
|
static $cache = [];
|
||||||
|
$data = \var_export($data, true);
|
||||||
|
if (isset($cache[$data])) {
|
||||||
|
return $cache[$data];
|
||||||
|
}
|
||||||
|
$res = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse('<?php '.$data);
|
||||||
|
if ($res === null || empty($res) || !$res[0] instanceof Expression || !isset($res[0]->expr)) {
|
||||||
|
throw new \RuntimeException('An invalid literal was provided!');
|
||||||
|
}
|
||||||
|
return $cache[$data] = $res[0]->expr;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getConfig(string $key)
|
||||||
|
{
|
||||||
|
return $this->config[$key];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function setConfig(string $key, $value): void
|
||||||
|
{
|
||||||
|
$this->config[$key] = $value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function needs()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function extends()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Plugin;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\FunctionLike;
|
||||||
|
use PhpParser\Node\NullableType;
|
||||||
|
use PhpParser\Node\Param;
|
||||||
|
use PhpParser\Node\UnionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all usages of a certain type in typehints.
|
||||||
|
*/
|
||||||
|
class TypeHintStripper extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Strip typehint.
|
||||||
|
*
|
||||||
|
* @param null|Identifier|Name|NullableType|UnionType &$type Type
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function strip(?Node &$type): void
|
||||||
|
{
|
||||||
|
if (!$type || $type instanceof UnionType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($type instanceof NullableType && $this->getConfig('nullable')) {
|
||||||
|
$type = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$throwableType = $type instanceof NullableType ? $type->type : $type;
|
||||||
|
// Make this less ugly when we implement a namespace context
|
||||||
|
if (\in_array($throwableType->toString(), $this->getConfig('types'))) {
|
||||||
|
$type = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remove param.
|
||||||
|
*
|
||||||
|
* @param Param $node Parameter
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterParam(Param $node): void
|
||||||
|
{
|
||||||
|
$this->strip($node->type);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Strip return throwable type.
|
||||||
|
*
|
||||||
|
* @param FunctionLike $func Function
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterFuncReturn(FunctionLike $func): void
|
||||||
|
{
|
||||||
|
$this->strip($func->getReturnType());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel;
|
||||||
|
|
||||||
|
use PhpParser\NodeVisitor;
|
||||||
|
|
||||||
|
interface PluginInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Specify which plugins does this plugin require.
|
||||||
|
*
|
||||||
|
* At each level, the traverser will execute the enter and leave methods of the specified plugins, completing a full AST traversal before starting a new AST traversal for the current plugin.
|
||||||
|
* Of course, this increases complexity as it forces an additional traversal of the AST.
|
||||||
|
*
|
||||||
|
* When possible, use the extends method to reduce complexity.
|
||||||
|
*
|
||||||
|
* @return array|string Plugin name(s)
|
||||||
|
*
|
||||||
|
* @psalm-return array<class-string<Plugin|NodeVisitor>>|class-string<Plugin|NodeVisitor>
|
||||||
|
*/
|
||||||
|
public function needs();
|
||||||
|
/**
|
||||||
|
* Specify which plugins does this plugin extends.
|
||||||
|
*
|
||||||
|
* At each depth level, the traverser will first execute the enter|leave methods of the specified plugins, then immediately execute the enter|leave methods of the current plugin.
|
||||||
|
*
|
||||||
|
* This is preferred, and allows only a single traversal of the AST.
|
||||||
|
*
|
||||||
|
* @return array|string Plugin name(s)
|
||||||
|
*
|
||||||
|
* @psalm-return array<class-string<Plugin|NodeVisitor>>|class-string<Plugin|NodeVisitor>
|
||||||
|
*/
|
||||||
|
public function extends();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration key
|
||||||
|
*
|
||||||
|
* @param string $key Key
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getConfig(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set configuration key
|
||||||
|
*
|
||||||
|
* @param string $key Key
|
||||||
|
* @param mixed $value Value
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setConfig(string $key, $value): void;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Spatie\Php7to5\NodeVisitors;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
|
class MethodCallReplacer extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function leaveNode(Node $node)
|
||||||
|
{
|
||||||
|
if (!$node instanceof Node\Expr\MethodCall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$value = &$node->var;
|
||||||
|
if (!$value instanceof Node\Expr\Clone_ &&
|
||||||
|
!$value instanceof Node\Expr\Yield_ &&
|
||||||
|
!$value instanceof Node\Expr\Closure
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$value = new Node\Expr\FuncCall(new Node\Name('\\returnMe'), [$value]);
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Spatie\Php7to5\NodeVisitors;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
|
class YieldFromReplacer extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $foreachYield;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function leaveNode(Node $node)
|
||||||
|
{
|
||||||
|
if (!$node instanceof Node\Expr\YieldFrom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$generator = $node->expr;
|
||||||
|
|
||||||
|
return new Node\Expr\Yield_($generator);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Spatie\Php7to5\NodeVisitors;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeTraverser;
|
||||||
|
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 YieldReturnDetector extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
protected $hasYield = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function enterNode(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof Node\FunctionLike) {
|
||||||
|
$this->hasYield []= $node;
|
||||||
|
}
|
||||||
|
if ($node instanceof Node\Expr\Yield_ ||
|
||||||
|
$node instanceof Node\Expr\YieldFrom
|
||||||
|
) {
|
||||||
|
end($this->hasYield)->hasYield = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function leaveNode(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof Node\FunctionLike) {
|
||||||
|
array_pop($this->hasYield);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Spatie\Php7to5\NodeVisitors;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
|
class YieldReturnReplacer extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
protected $functions = [];
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function enterNode(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof Node\FunctionLike) {
|
||||||
|
$this->functions[] = $node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function leaveNode(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof Node\FunctionLike) {
|
||||||
|
array_pop($this->functions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$node instanceof Node\Stmt\Return_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($node->expr === null) {
|
||||||
|
return new Node\Stmt\Return_();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(end($this->functions)->hasYield ?? false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $node->expr;
|
||||||
|
|
||||||
|
$newReturn = new Node\Expr\Yield_(
|
||||||
|
new Node\Expr\New_(
|
||||||
|
new Node\Expr\ConstFetch(
|
||||||
|
new Node\Name('\YieldReturnValue')
|
||||||
|
),
|
||||||
|
[$value]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$stmts = [$newReturn, new Node\Stmt\Return_()];
|
||||||
|
return $stmts;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Spatie\Php7to5\NodeVisitors;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $anonymousClassNodes = [];
|
||||||
|
public static $count = 0;
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function leaveNode(Node $node)
|
||||||
|
{
|
||||||
|
if (!$node instanceof Node\Expr\New_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classNode = $node->class;
|
||||||
|
if (!$classNode instanceof Node\Stmt\Class_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newClassName = 'AnonymousClass'.(self::$count++);
|
||||||
|
|
||||||
|
$classNode->name = $newClassName;
|
||||||
|
|
||||||
|
$this->anonymousClassNodes[] = $classNode;
|
||||||
|
|
||||||
|
// Generate new code that instantiate our new class
|
||||||
|
$newNode = new Node\Expr\New_(
|
||||||
|
new Node\Expr\ConstFetch(
|
||||||
|
new Node\Name($newClassName)
|
||||||
|
),
|
||||||
|
$node->args
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
|
use PhpParser\Node\Expr\FuncCall;
|
||||||
|
use PhpParser\Node\Expr\StaticCall;
|
||||||
|
use PhpParser\Node\Expr\Variable;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
|
||||||
|
class ClosureCallReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replace composite function calls
|
||||||
|
*
|
||||||
|
* @param FuncCall $node Function call
|
||||||
|
*
|
||||||
|
* @return StaticCall|null
|
||||||
|
*/
|
||||||
|
public function enter(FuncCall $node): ?StaticCall
|
||||||
|
{
|
||||||
|
$name = $node->name;
|
||||||
|
if ($name instanceof Name || $name instanceof Variable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
\array_unshift($node->args, new Arg($name));
|
||||||
|
return self::callPoly('callMe', ...$node->args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call provided argument.
|
||||||
|
*
|
||||||
|
* @param callable $callable Callable
|
||||||
|
* @param mixed ...$arguments Arguments
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function callMe(callable $callable, ...$arguments)
|
||||||
|
{
|
||||||
|
return $callable($arguments);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||||
|
use PhpParser\Node\Expr\ClassConstFetch;
|
||||||
|
use PhpParser\Node\Expr\Isset_;
|
||||||
|
use PhpParser\Node\Expr\Yield_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix certain compound access statements.
|
||||||
|
*/
|
||||||
|
class CompoundAccess extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replace non-simple isset calls.
|
||||||
|
*
|
||||||
|
* @param Isset_ $node Isset node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterIsset(Isset_ $node): void
|
||||||
|
{
|
||||||
|
foreach ($node->vars as &$var) {
|
||||||
|
if (!$var instanceof ArrayDimFetch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$var->var instanceof ClassConstFetch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$var->var = self::callPoly('returnMe', $var->var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fix yield array access.
|
||||||
|
*
|
||||||
|
* @param ArrayDimFetch $node Node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterArrayYield(ArrayDimFetch $node): void
|
||||||
|
{
|
||||||
|
if (!$node->var instanceof Node\Expr\Yield_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$node->var = self::callPoly('returnMe', $node->var);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fix yield array access
|
||||||
|
*
|
||||||
|
* @param Yield_ $node Yield
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterYield(Yield_ $node): void
|
||||||
|
{
|
||||||
|
$value = &$node->value;
|
||||||
|
if ($value instanceof Node\Expr\Variable && $value->name !== "this") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($value instanceof Node\Expr\FuncCall ||
|
||||||
|
$value instanceof Node\Expr\MethodCall ||
|
||||||
|
$value instanceof Node\Expr\StaticCall ||
|
||||||
|
$value instanceof Node\Scalar
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$value = self::callPoly('returnMe', $value);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the data provided.
|
||||||
|
*
|
||||||
|
* @param mixed $data Data
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*
|
||||||
|
* @template T
|
||||||
|
*
|
||||||
|
* @psalm-param T $data data
|
||||||
|
*
|
||||||
|
* @psalm-return T
|
||||||
|
*/
|
||||||
|
public static function returnMe($data)
|
||||||
|
{
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\FuncCall;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Stmt\Const_;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts define() arrays into const arrays
|
||||||
|
*/
|
||||||
|
class DefineArrayReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Convert define() arrays into const arrays
|
||||||
|
*
|
||||||
|
* @param FuncCall $node Node
|
||||||
|
*
|
||||||
|
* @return Const_|null
|
||||||
|
*/
|
||||||
|
public function enter(FuncCall $node): ?Const_
|
||||||
|
{
|
||||||
|
if (!$node->name instanceof Name || $node->name->toString() != 'define') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nameNode = $node->args[0]->value;
|
||||||
|
$valueNode = $node->args[1]->value;
|
||||||
|
|
||||||
|
if (!$valueNode instanceof Node\Expr\Array_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$constNode = new Node\Const_($nameNode->value, $valueNode);
|
||||||
|
|
||||||
|
return new Node\Stmt\Const_([$constNode]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt\GroupUse;
|
||||||
|
use PhpParser\Node\Stmt\Use_;
|
||||||
|
use PhpParser\Node\Stmt\UseUse;
|
||||||
|
|
||||||
|
class GroupUseReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replace group use with multiple use statements
|
||||||
|
*
|
||||||
|
* @param GroupUse $node Group use statement
|
||||||
|
*
|
||||||
|
* @return Use_[]
|
||||||
|
*/
|
||||||
|
public function leave(GroupUse $node): array
|
||||||
|
{
|
||||||
|
$nodePrefixParts = $node->prefix->parts;
|
||||||
|
|
||||||
|
return \array_map(fn (UseUse $useNode) => $this->createUseNode($nodePrefixParts, $useNode), $node->uses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create separate use node.
|
||||||
|
*
|
||||||
|
* @param string[] $nodePrefixParts Use prefix
|
||||||
|
* @param UseUse $useNode Current use node
|
||||||
|
*
|
||||||
|
* @return Use_ New use node
|
||||||
|
*/
|
||||||
|
protected function createUseNode(array $nodePrefixParts, UseUse $useNode): Use_
|
||||||
|
{
|
||||||
|
$nodePrefixParts []= $useNode->name;
|
||||||
|
|
||||||
|
$nameNode = new Node\Name($nodePrefixParts);
|
||||||
|
|
||||||
|
return new Use_([new UseUse($nameNode, $useNode->alias)], $useNode->type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\BinaryOp\Coalesce;
|
||||||
|
use PhpParser\Node\Expr\StaticCall;
|
||||||
|
|
||||||
|
class NullCoalesceReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replace null coalesce
|
||||||
|
*
|
||||||
|
* @param Coalesce $node Coalesce
|
||||||
|
*
|
||||||
|
* @return StaticCall
|
||||||
|
*/
|
||||||
|
public function enter(Coalesce $node): StaticCall
|
||||||
|
{
|
||||||
|
if (!($node->left instanceof Node\Expr\ErrorSuppress)) {
|
||||||
|
$node->left = new Node\Expr\ErrorSuppress($node->left);
|
||||||
|
}
|
||||||
|
return self::callPoly('coalesce', $node->left, $node->right);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Coalesce.
|
||||||
|
*
|
||||||
|
* @param null|mixed $ifNotNull If not null, return this
|
||||||
|
* @param mixed $then Else this
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function coalesce($ifNotNull, $then)
|
||||||
|
{
|
||||||
|
return isset($ifNotNull) ?: $then;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
class ReservedNameReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function leaveNode(Node $node): void
|
||||||
|
{
|
||||||
|
if (!$node instanceof Node\Expr\MethodCall &&
|
||||||
|
!$node instanceof Node\Expr\StaticCall &&
|
||||||
|
!$node instanceof Node\Stmt\ClassMethod &&
|
||||||
|
!$node instanceof Node\Expr\ClassConstFetch &&
|
||||||
|
!$node instanceof Node\Const_
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$name = &$node->name;
|
||||||
|
if (!\is_string($name) || !\in_array(\strtolower($name), ['continue', 'empty', 'use', 'default', 'echo'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$name .= '_';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use Phabel\Plugin\TypeHintStripper;
|
||||||
|
|
||||||
|
class ScalarTypeHintsRemover extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Alias.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function needs(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TypeHintStripper::class => [
|
||||||
|
'types' => ['int', 'integer', 'float', 'string', 'bool', 'boolean']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node\Expr\BinaryOp\Spaceship;
|
||||||
|
use PhpParser\Node\Expr\StaticCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfill spaceship operator.
|
||||||
|
*/
|
||||||
|
class SpaceshipOperatorReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replace spaceship operator.
|
||||||
|
*
|
||||||
|
* @param Spaceship $node Node
|
||||||
|
*
|
||||||
|
* @return StaticCall
|
||||||
|
*/
|
||||||
|
public function enter(Spaceship $node): StaticCall
|
||||||
|
{
|
||||||
|
return self::callPoly('spaceship', $node->left, $node->right);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Spacesip operator.
|
||||||
|
*
|
||||||
|
* @param integer|string|float $a A
|
||||||
|
* @param integer|string|float $b B
|
||||||
|
*
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public static function spaceship($a, $b): int
|
||||||
|
{
|
||||||
|
return $a < $b ? -1 : ($a === $b ? 0 : 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use PhpParser\Node\Stmt\Declare_;
|
||||||
|
use PhpParser\Node\Stmt\DeclareDeclare;
|
||||||
|
use PhpParser\NodeTraverser;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
|
class StrictTypesDeclareStatementRemover extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function leave(Declare_ $node): ?int
|
||||||
|
{
|
||||||
|
$node->declares = \array_filter($node->declares, fn (DeclareDeclare $declare) => $declare->key->name !== 'strict_types');
|
||||||
|
|
||||||
|
if (empty($node->declares)) {
|
||||||
|
return NodeTraverser::REMOVE_NODE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php70;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use Phabel\Plugin\TypeHintStripper;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
|
||||||
|
use PhpParser\Node\Expr\Instanceof_;
|
||||||
|
use PhpParser\Node\Name\FullyQualified;
|
||||||
|
use PhpParser\Node\Param;
|
||||||
|
use PhpParser\Node\Stmt\TryCatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace \Throwable usages.
|
||||||
|
*/
|
||||||
|
class ThrowableReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check if type string is \Throwable or Throwable.
|
||||||
|
*
|
||||||
|
* @param string $type Type string
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
private function isThrowable(string $type): bool
|
||||||
|
{
|
||||||
|
// Make this less ugly when we implement a namespace context
|
||||||
|
return $type === \Throwable::class || $type === 'Throwable';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Split instance of \Throwable.
|
||||||
|
*
|
||||||
|
* @param Instanceof_ $node
|
||||||
|
*
|
||||||
|
* @return ?BooleanOr
|
||||||
|
*/
|
||||||
|
public function enterInstanceOf(Instanceof_ $node): ?BooleanOr
|
||||||
|
{
|
||||||
|
if (!$this->isThrowable($node->class)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new BooleanOr(
|
||||||
|
new Instanceof_($node->expr, new FullyQualified('Exception')),
|
||||||
|
new Instanceof_($node->expr, new FullyQualified('Error'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Substitute try-catch.
|
||||||
|
*
|
||||||
|
* @param TryCatch $node TryCatch node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterTryCatch(TryCatch $node): void
|
||||||
|
{
|
||||||
|
foreach ($node->catches as $catch) {
|
||||||
|
$alreadyHasError = false;
|
||||||
|
$needs = false;
|
||||||
|
foreach ($catch->types as &$type) {
|
||||||
|
if ($type instanceof FullyQualified &&
|
||||||
|
$type->getLast() === "Error") {
|
||||||
|
$alreadyHasError = true;
|
||||||
|
}
|
||||||
|
if ($this->isThrowable($type)) {
|
||||||
|
$needs = true;
|
||||||
|
$type = new FullyQualified('Exception');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($needs && !$alreadyHasError) {
|
||||||
|
$catch->types[] = new FullyQualified('Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other transforms.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function extends(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TypeHintStripper::class => [
|
||||||
|
'type' => [
|
||||||
|
\Throwable::class,
|
||||||
|
'Throwable'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php71;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node\Expr\Array_;
|
||||||
|
use PhpParser\Node\Expr\Assign;
|
||||||
|
use PhpParser\Node\Expr\List_;
|
||||||
|
use PhpParser\Node\Stmt\Foreach_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces [] array list syntax.
|
||||||
|
*/
|
||||||
|
class ArrayList extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Caled when entering Foreach_ node.
|
||||||
|
*
|
||||||
|
* @param Foreach_ $node Node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterForeach(Foreach_ $node): void
|
||||||
|
{
|
||||||
|
if ($node->valueVar instanceof Array_) {
|
||||||
|
self::replaceTypeInPlace($node->valueVar, List_::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called when entering assignment node.
|
||||||
|
*
|
||||||
|
* @param Assign $node Node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterAssign(Assign $node): void
|
||||||
|
{
|
||||||
|
if ($node->var instanceof Array_) {
|
||||||
|
self::replaceTypeInPlace($node->var, List_::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called when entering list for nested lists.
|
||||||
|
*
|
||||||
|
* @param List_ $node Node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterList(List_ $node): void
|
||||||
|
{
|
||||||
|
foreach ($node->items as $item) {
|
||||||
|
if ($item->value instanceof Array_) {
|
||||||
|
self::replaceTypeInPlace($item->value, List_::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php71;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Expr\ClassConstFetch;
|
||||||
|
use PhpParser\Node\Stmt\ClassConst;
|
||||||
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the class constant visibility modifiers (PHP 7.1).
|
||||||
|
*/
|
||||||
|
class ClassConstantVisibilityModifiersRemover extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Makes public private and protected class constants.
|
||||||
|
*
|
||||||
|
* @param ClassConst $node Constant
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enter(ClassConst $node): void
|
||||||
|
{
|
||||||
|
$node->flags = 0; // Remove constant modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function needs(): string
|
||||||
|
{
|
||||||
|
return NameResolver::class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php71;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node\Expr\Array_;
|
||||||
|
use PhpParser\Node\Expr\ArrayItem;
|
||||||
|
use PhpParser\Node\Expr\Assign;
|
||||||
|
use PhpParser\Node\Expr\List_;
|
||||||
|
use PhpParser\Node\Stmt\Foreach_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfills keyed list assignment.
|
||||||
|
*/
|
||||||
|
class ListKey extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parse list foreach with custom keys.
|
||||||
|
*
|
||||||
|
* @param Foreach_ $node Foreach
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterForeach(Foreach_ $node): void
|
||||||
|
{
|
||||||
|
if (!$node->valueVar instanceof List_ || !$this->shouldSplit($node->valueVar)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[$node->valueVar, $array] = $this->splitList($node->valueVar);
|
||||||
|
$node->expr = self::callPoly('destructure', $array, $node->expr);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Parse list assignment with custom keys.
|
||||||
|
*
|
||||||
|
* @param Assign $node List assignment
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enterAssign(Assign $node): void
|
||||||
|
{
|
||||||
|
if (!$node->var instanceof List_ || !$this->shouldSplit($node->var)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[$node->var, $array] = $this->splitList($node->var);
|
||||||
|
$node->expr = self::callPoly('destructure', $array, $node->expr);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Whether this is a keyed list.
|
||||||
|
*
|
||||||
|
* @param List_ $list List
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
private function shouldSplit(List_ $list): bool
|
||||||
|
{
|
||||||
|
return isset($list->items[0]->key);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Split keyed list into new list assignment and array to pass to destructure function.
|
||||||
|
*
|
||||||
|
* @param List_ $list Keyed list
|
||||||
|
*
|
||||||
|
* @return array{0: List_, 1: Array_}
|
||||||
|
*/
|
||||||
|
private function splitList(List_ $list): array
|
||||||
|
{
|
||||||
|
$newList = [];
|
||||||
|
$keys = [];
|
||||||
|
$key = 0; // Technically list assignment does not support mixed keys, but we need this for nested assignments
|
||||||
|
foreach ($list->items as $item) {
|
||||||
|
if ($item) {
|
||||||
|
$curKey = $item->key ?? $key++;
|
||||||
|
$item->key = null;
|
||||||
|
if ($item->value instanceof List_) {
|
||||||
|
[$item->value, $keys[$curKey]] = $this->splitList($list);
|
||||||
|
} else {
|
||||||
|
$keys[$curKey] = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$newList []= null;
|
||||||
|
$keys[$key++] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** @var Array_ */
|
||||||
|
$keys = self::toLiteral($keys);
|
||||||
|
return [new List_($newList), $keys];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Destructure array.
|
||||||
|
*
|
||||||
|
* @param array $keys Custom keys
|
||||||
|
* @param array $array Array
|
||||||
|
*
|
||||||
|
* @psalm-param array<string, null|array> $keys Custom keys
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function destructure(array $keys, array $array): array
|
||||||
|
{
|
||||||
|
$res = [];
|
||||||
|
foreach ($keys as $key => $type) {
|
||||||
|
if ($type === null) {
|
||||||
|
$res[] = $array[$key];
|
||||||
|
} else {
|
||||||
|
$res[] = self::destructure($type, $array[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function needs(): string
|
||||||
|
{
|
||||||
|
return ArrayList::class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel\Target\Php71;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use PhpParser\Node\Stmt\TryCatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace compound catches.
|
||||||
|
*/
|
||||||
|
class MultipleCatchReplacer extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replace compound catches.
|
||||||
|
*
|
||||||
|
* Do this while leaving to avoid re-iterating uselessly on duplicated code.
|
||||||
|
*
|
||||||
|
* @param TryCatch $node Catch stmt
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function leave(TryCatch $node): void
|
||||||
|
{
|
||||||
|
$catches = [];
|
||||||
|
foreach ($node->catches as $catch) {
|
||||||
|
if (\count($catch->types) === 1) {
|
||||||
|
$catches []= $catch;
|
||||||
|
} else {
|
||||||
|
foreach ($catch->types as $type) {
|
||||||
|
$ncatch = clone $catch;
|
||||||
|
$ncatch->types = [$type];
|
||||||
|
$catches []= $ncatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$node->catches = $catches;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Extends throwable replacer
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @psalm-return class-string
|
||||||
|
*/
|
||||||
|
public function extends(): string
|
||||||
|
{
|
||||||
|
return ThrowableReplacer::class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Spatie\Php7to5\NodeVisitors;
|
||||||
|
|
||||||
|
use Phabel\Plugin;
|
||||||
|
use Phabel\Plugin\TypeHintStripper;
|
||||||
|
|
||||||
|
class NullableTypeRemover extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Remove nullable typehint
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function needs(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TypeHintStripper::class => [
|
||||||
|
'nulable' => true
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Phabel;
|
||||||
|
|
||||||
|
class Traverser
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue