This commit is contained in:
Daniil Gentili 2020-08-23 20:54:56 +02:00
parent 47562e4745
commit d0694ab126
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
17 changed files with 578 additions and 96 deletions

View File

@ -31,7 +31,7 @@ class Plugin implements PluginInterface, EventSubscriberInterface
{
$repoManager = $composer->getRepositoryManager();
$repos = $repoManager->getRepositories();
$repoManager->prependRepository()
$repoManager->prependRepository();
$this->io = $io;
}
@ -48,15 +48,14 @@ class Plugin implements PluginInterface, EventSubscriberInterface
/**
* Emitted before composer solves dependencies
* Emitted before composer solves dependencies.
*
* @param InstallerEvent $event Event
*
*
* @return void
*/
public function onDependencySolve(InstallerEvent $event): void
{
var_dump($event);
\var_dump($event);
}
}

View File

@ -30,10 +30,16 @@ class Context
*/
public function insertBefore(Node $node, Node ...$nodes): void
{
if (empty($nodes)) {
return;
}
$subNode = $node->getAttribute('currentNode');
$subNodeIndex = $node->getAttribute('currentNodeIndex');
\array_splice($node->{$subNode}, $subNodeIndex, 0, $nodes);
$node->setAttribute('currentNodeIndex', $subNodeIndex+\count($nodes));
$skips = $node->getAttribute('skipNodes', []);
$skips []= $subNodeIndex+\count($nodes);
$node->setAttribute('skipNodes', $skips);
$node->setAttribute('currentNodeIndex', $subNodeIndex - 1);
}
/**
* Insert nodes after node.

View File

@ -8,6 +8,7 @@ use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
@ -146,8 +147,8 @@ abstract class Plugin implements PluginInterface
/**
* Call function.
*
* @param class-string|array{0: class-string, 1: string} $name Function name
* @param Expr|Arg ...$parameters Parameters
* @param class-string|array{0: class-string, 1: string}|callable-string $name Function name
* @param Expr|Arg ...$parameters Parameters
*
* @return FuncCall|StaticCall
*/
@ -168,6 +169,20 @@ abstract class Plugin implements PluginInterface
{
return self::call([static::class, $name], ...$parameters);
}
/**
* Call method of object.
*
* @param Expr $name Object name
* @param string $method Method
* @param Expr|Arg ...$parameters Parameters
*
* @return MethodCall
*/
public static function callMethod(Expr $name, string $method, ...$parameters): MethodCall
{
$parameters = \array_map(fn ($data) => $data instanceof Arg ? $data : new Arg($data), $parameters);
return new MethodCall($name, $method, $parameters);
}
/**
* Convert array, int or other literal to node.
*

View File

@ -1,28 +0,0 @@
<?php
namespace Spatie\Php7to5\NodeVisitors;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
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);
}
}

241
src/Plugin/NameResolver.php Normal file
View File

@ -0,0 +1,241 @@
<?php declare(strict_types=1);
namespace Phabel\Plugin;
use Phabel\Plugin;
use PhpParser\ErrorHandler;
use PhpParser\NameContext;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
class NameResolver extends Plugin
{
/** @var NameContext Naming context */
protected $nameContext;
/**
* Constructs a name resolution visitor.
*
* Options:
* * preserveOriginalNames (default false): An "originalName" attribute will be added to
* all name nodes that underwent resolution.
* * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
* resolvedName attribute is added. (Names that cannot be statically resolved receive a
* namespacedName attribute, as usual.)
*
* @param ErrorHandler|null $errorHandler Error handler
* @param array $options Options
*/
public function __construct(ErrorHandler $errorHandler = null, array $options = [])
{
$this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
}
/**
* Get name resolution context.
*
* @return NameContext
*/
public function getNameContext(): NameContext
{
return $this->nameContext;
}
public function beforeTraverse(array $nodes)
{
$this->nameContext->startNamespace();
return null;
}
public function enterNode(Node $node)
{
if ($node instanceof Stmt\Namespace_) {
$this->nameContext->startNamespace($node->name);
} elseif ($node instanceof Stmt\Use_) {
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, null);
}
} elseif ($node instanceof Stmt\GroupUse) {
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, $node->prefix);
}
} elseif ($node instanceof Stmt\Class_) {
if (null !== $node->extends) {
$node->extends = $this->resolveClassName($node->extends);
}
foreach ($node->implements as &$interface) {
$interface = $this->resolveClassName($interface);
}
if (null !== $node->name) {
$this->addNamespacedName($node);
}
} elseif ($node instanceof Stmt\Interface_) {
foreach ($node->extends as &$interface) {
$interface = $this->resolveClassName($interface);
}
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Trait_) {
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Function_) {
$this->addNamespacedName($node);
$this->resolveSignature($node);
} elseif ($node instanceof Stmt\ClassMethod
|| $node instanceof Expr\Closure
|| $node instanceof Expr\ArrowFunction
) {
$this->resolveSignature($node);
} elseif ($node instanceof Stmt\Property) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);
}
} elseif ($node instanceof Expr\StaticCall
|| $node instanceof Expr\StaticPropertyFetch
|| $node instanceof Expr\ClassConstFetch
|| $node instanceof Expr\New_
|| $node instanceof Expr\Instanceof_
) {
if ($node->class instanceof Name) {
$node->class = $this->resolveClassName($node->class);
}
} elseif ($node instanceof Stmt\Catch_) {
foreach ($node->types as &$type) {
$type = $this->resolveClassName($type);
}
} elseif ($node instanceof Expr\FuncCall) {
if ($node->name instanceof Name) {
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
}
} elseif ($node instanceof Expr\ConstFetch) {
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
} elseif ($node instanceof Stmt\TraitUse) {
foreach ($node->traits as &$trait) {
$trait = $this->resolveClassName($trait);
}
foreach ($node->adaptations as $adaptation) {
if (null !== $adaptation->trait) {
$adaptation->trait = $this->resolveClassName($adaptation->trait);
}
if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
foreach ($adaptation->insteadof as &$insteadof) {
$insteadof = $this->resolveClassName($insteadof);
}
}
}
}
return null;
}
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null)
{
// Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration
$type |= $use->type;
$this->nameContext->addAlias(
$name,
(string) $use->getAlias(),
$type,
$use->getAttributes()
);
}
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
private function resolveSignature($node)
{
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
}
$node->returnType = $this->resolveType($node->returnType);
}
private function resolveType($node)
{
if ($node instanceof Name) {
return $this->resolveClassName($node);
}
if ($node instanceof Node\NullableType) {
$node->type = $this->resolveType($node->type);
return $node;
}
if ($node instanceof Node\UnionType) {
foreach ($node->types as &$type) {
$type = $this->resolveType($type);
}
return $node;
}
return $node;
}
/**
* Resolve name, according to name resolver options.
*
* @param Name $name Function or constant name to resolve
* @param int $type One of Stmt\Use_::TYPE_*
*
* @return Name Resolved name, or original name with attribute
*/
protected function resolveName(Name $name, int $type): Name
{
if (!$this->replaceNodes) {
$resolvedName = $this->nameContext->getResolvedName($name, $type);
if (null !== $resolvedName) {
$name->setAttribute('resolvedName', $resolvedName);
} else {
$name->setAttribute('namespacedName', FullyQualified::concat(
$this->nameContext->getNamespace(),
$name,
$name->getAttributes()
));
}
return $name;
}
if ($this->preserveOriginalNames) {
// Save the original name
$originalName = $name;
$name = clone $originalName;
$name->setAttribute('originalName', $originalName);
}
$resolvedName = $this->nameContext->getResolvedName($name, $type);
if (null !== $resolvedName) {
return $resolvedName;
}
// unqualified names inside a namespace cannot be resolved at compile-time
// add the namespaced version of the name as an attribute
$name->setAttribute('namespacedName', FullyQualified::concat(
$this->nameContext->getNamespace(),
$name,
$name->getAttributes()
));
return $name;
}
protected function resolveClassName(Name $name)
{
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
}
protected function addNamespacedName(Node $node)
{
$node->namespacedName = Name::concat(
$this->nameContext->getNamespace(),
(string) $node->name
);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Phabel\Plugin;
use Phabel\Context;
use Phabel\Plugin;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Scalar\String_;
use SplQueue;
/**
* Optimizes concatenation of multiple strings.
*/
class StringConcatOptimizer extends Plugin
{
private function enqueue(Concat $concat, SplQueue $queue)
{
if ($concat->left instanceof Concat) {
$this->enqueue($concat->left, $queue);
} else {
$queue->enqueue($concat->left);
}
if ($concat->right instanceof Concat) {
$this->enqueue($concat->right, $queue);
} else {
$queue->enqueue($concat->right);
}
}
public function enter(Concat $concat, Context $ctx): ?Node
{
if ($ctx->parents->top() instanceof Concat) {
return;
}
$concatQueue = new SplQueue;
$this->enqueue($concat, $concatQueue);
$newQueue = new SplQueue;
$prevNode = $concatQueue->dequeue();
while ($node = $concatQueue->dequeue()) {
if ($node instanceof String_ && $prevNode instanceof String_) {
$prevNode = new String_($prevNode->value.$node->value);
} else {
$newQueue->enqueue($prevNode);
$prevNode = $node;
}
}
$newQueue->enqueue($prevNode);
if ($newQueue->count() === 1) {
return $newQueue->dequeue();
}
$concat = new Concat($newQueue->dequeue(), $newQueue->dequeue());
while ($node = $newQueue->dequeue()) {
$concat = new Concat($concat, $newQueue->dequeue());
}
return $concat;
}
}

View File

@ -4,7 +4,10 @@ namespace Phabel\Plugin;
use Phabel\Context;
use Phabel\Plugin;
use Phabel\Target\Php74\ArrowClosure;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignRef;
@ -27,6 +30,7 @@ use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\MagicConst\Function_ as MagicConstFunction_;
use PhpParser\Node\Scalar\MagicConst\Method;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\If_;
@ -42,27 +46,53 @@ use SplStack;
*/
class TypeHintStripper extends Plugin
{
private const IGNORE_RETURN = 0;
private const VOID_RETURN = 1;
private const TYPE_RETURN = 2;
/**
* Stack.
*
* @template T as array{0: self::IGNORE_RETURN|self::VOID_RETURN}|array{0: self::TYPE_RETURN, 1: Node, 2: bool, 3: bool, 4: Node, 5: BooleanNot}
*
* @var SplStack<T>
*/
private SplStack $stack;
/**
* Constructor.
*/
public function __construct()
{
$this->stack = $stack;
$this->stack = new SplStack;
}
/**
* Convert a function to a closure.
*/
private function toClosure(FunctionLike &$func): void
{
if ($func instanceof ArrowFunction) {
$nodes = [];
foreach ($func->getSubNodeNames() as $node) {
$nodes[$node] = $func->{$nodes};
}
$func = new Closure($nodes, $func->getAttributes());
$func = ArrowClosure::enterClosure($func);
}
}
/**
* Generate.
*
* @param Variable $var Variable to check
* @param (Name|Identifier)[] $types Types to check
* @param boolean $fromNullable Whether this type is nullable
*
* @return array{0: bool, 1: Node, 2: BooleanNot} Whether the polyfilled gettype should be used, the error message, the condition
*/
private function generateConditions(Variable $var, array $types, bool $fromNullable = false): array
{
/** @var bool Whether no explicit classes were referenced */
$noOopTypes = true;
/** @var string[] */
$typeNames = [];
/** @var Expr[] */
$conditions = [];
/** @var string Last string type name */
$stringType = '';
foreach ($types as &$type) {
foreach ($types as $type) {
$typeNames []= $type->toString();
if ($type instanceof Identifier) {
@ -80,11 +110,11 @@ class TypeHintStripper extends Plugin
$stringType = new String_($typeName === 'callable' ?
$typeName :
($typeName === 'object' ? 'an object' : "of type $typeName"));
$type = Plugin::call("is_$typeName", $var);
$conditions []= Plugin::call("is_$typeName", $var);
break;
case 'iterable':
$stringType = new String_('iterable');
$type = new BooleanOr(
$conditions []= new BooleanOr(
Plugin::call("is_array", $var),
new Instanceof_($var, new Name(\Traversable::class))
);
@ -92,24 +122,29 @@ class TypeHintStripper extends Plugin
default:
$noOopTypes = false;
$stringType = $type->isSpecialClassName() ?
new Concat(new String_("an instance of "), new ClassConstFetch($type, new Identifier('class'))) :
new Concat(new String_("an instance of "), new ClassConstFetch(new Name($typeName), new Identifier('class'))) :
new String_("an instance of ".$type->toString());
$type = new Instanceof_($var, $type);
$conditions []= new Instanceof_($var, new Name($typeName));
}
} else {
$noOopTypes = false;
$stringType = new String_("an instance of ".$type->toString());
if ($type->toString() === 'Stringable' || $type->toString() === \Stringable::class) {
$type = Plugin::callPoly("is_stringable", $var);
} else {
$type = new Instanceof_($var, $type);
}
$conditions []= new Instanceof_($var, $type);
}
}
if (\count($typeNames) > 1) {
$stringType = new String_(\implode("|", $typeNames));
}
$condition = new BooleanNot(\count($types) === 1 ? $types[0] : \array_reduce($types, fn (Node $a, Node $b): BooleanOr => new BooleanOr($a, $b)));
if ($fromNullable) {
$stringType = new Concat($stringType, new String_(' or null'));
$conditions []= Plugin::call("is_null", $var);
}
$initial = \array_shift($conditions);
$condition = new BooleanNot(
empty($conditions)
? $initial
: \array_reduce($conditions, fn (Expr $a, Expr $b): BooleanOr => new BooleanOr($a, $b), $initial)
);
return [$noOopTypes, $stringType, $condition];
}
/**
@ -118,12 +153,12 @@ class TypeHintStripper extends Plugin
* @param Variable $var Variable
* @param null|Identifier|Name|NullableType|UnionType $type Type
*
* @return array{0: bool, 1: Node, 2: BooleanNot} Conditions for if
* @return null|array{0: bool, 1: Node, 2: BooleanNot} Whether the polyfilled gettype should be used, the error message, the condition
*/
private function strip(Variable $var, ?Node $type): array
private function strip(Variable $var, ?Node $type): ?array
{
if (!$type) {
return [];
return null;
}
if ($type instanceof UnionType && $this->getConfig('union', false)) {
return $this->generateConditions($var, $type->types);
@ -142,9 +177,9 @@ class TypeHintStripper extends Plugin
*
* @param FunctionLike $func Function
*
* @return void
* @return ?FunctionLike
*/
public function enterFunction(FunctionLike $func, Context $ctx): void
public function enterFunction(FunctionLike $func, Context $ctx): ?FunctionLike
{
$functionName = new Method();
if ($func instanceof ClassMethod) {
@ -169,9 +204,9 @@ class TypeHintStripper extends Plugin
$start = new Concat($start, new String_(", "));
$start = new Concat($start, $noOop ? self::call('gettype', $param->var) : self::callPoly('gettype', $param->var));
$start = new Concat($start, new String_(" given, called in "));
$start = new Concat($start, self::callPoly('trace', 0));
$start = new Concat($start, self::callPoly('trace', new LNumber(0)));
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [$start]))]);
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [new Arg($start)]))]);
if ($param->variadic) {
$stmts []= new Foreach_($param->var, new Variable('phabelVariadic'), ['keyVar' => new Variable('phabelVariadicIndex'), 'stmts' => [$if]]);
} else {
@ -180,34 +215,34 @@ class TypeHintStripper extends Plugin
}
if ($stmts) {
$this->toClosure($func);
$func->stmts = \array_merge($stmts, $func->getStmts());
$func->stmts = \array_merge($stmts, $func->getStmts() ?? []);
}
if ($this->getConfig('void', false) && $func->getReturnType() instanceof Identifier && $func->getReturnType()->toLowerString() === 'void') {
$this->toClosure($func);
$this->stack->push([true]);
$this->stack->push([self::VOID_RETURN]);
}
$var = new Variable('phabelReturn');
if (!$condition = $this->strip($var, $func->getReturnType(), false)) {
$this->stack->push([null]);
return;
if (!$condition = $this->strip($var, $func->getReturnType())) {
$this->stack->push([self::IGNORE_RETURN]);
return null;
}
$this->toClosure($func);
$this->stack->push([false, $functionName, $func->returnsByRef(), ...$condition]);
$this->stack->push([self::TYPE_RETURN, $functionName, $func->returnsByRef(), ...$condition]);
return $func;
}
public function enterReturn(Return_ $return, Context $ctx): ?Node
{
$current = $this->stack->top();
if ($current[0] === null) {
return;
if ($current[0] === self::IGNORE_RETURN) {
return null;
}
if ($current[0] === true) {
if ($current[0] === self::VOID_RETURN) {
if ($return->expr !== null) {
// This should be a transpilation error, wait for better stack traces before throwing here
return new Throw_(new New_(new FullyQualified(\ParseError::class), [new String_("A void function must not return a value")]));
}
return;
return null;
}
[, $functionName, $byRef, $noOop, $string, $condition] = $current;
@ -223,17 +258,31 @@ class TypeHintStripper extends Plugin
$start = new Concat($start, new String_(" returned in "));
$start = new Concat($start, self::callPoly('trace', 0));
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [$start]))]);
$if = new If_($condition, [new Throw_(new New_(new FullyQualified(\TypeError::class), [new Arg($start)]))]);
$return->expr = $var;
$ctx->insertBefore($ctx->parents->top(), $assign, $if);
}
public static function trace($index): string
/**
* Get trace string for errors.
*
* @param int $index Index
*
* @return string
*/
public static function trace($index)
{
$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[$index];
return ($trace['file'] ?? '').' on line '.($trace['line'] ?? '');
}
/**
* Get type string or object.
*
* @param mixed $object Object
*
* @return string
*/
public static function gettype($object)
{
if (\is_object($object)) {
@ -243,10 +292,11 @@ class TypeHintStripper extends Plugin
return \gettype($object);
}
public static function is_stringable($string): bool
{
return is_string($string) || (is_object($string) && method_exists($string, '__toString'));
}
/**
* Runwithafter.
*
* @return array
*/
public static function runWithAfter(): array
{
return [StringConcatOptimizer::class];

View File

@ -6,12 +6,12 @@ use PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* Root node
* Root node.
*/
class RootNode extends NodeAbstract
{
/**
* Children
* Children.
*
* @var Node[]
*/
@ -29,4 +29,4 @@ class RootNode extends NodeAbstract
{
return 'rootNode';
}
}
}

View File

@ -4,6 +4,7 @@ namespace Phabel\Target\Php70;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
use Phabel\Target\Php71\MultipleCatchReplacer;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\Instanceof_;
@ -25,7 +26,6 @@ class ThrowableReplacer extends Plugin
*/
private function isThrowable(string $type): bool
{
// Make this less ugly when we implement a namespace context
return $type === \Throwable::class || $type === 'Throwable';
}
/**
@ -86,7 +86,8 @@ class ThrowableReplacer extends Plugin
\Throwable::class,
'Throwable'
]
]
],
MultipleCatchReplacer::class => []
];
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Phabel\Target\Php70;
use Phabel\Context;
use Phabel\Plugin;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Expr\YieldFrom;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\While_;
class YieldFromReplacer extends Plugin
{
private string $phabelVar = '';
private int $phabelCount = 0;
public function shouldRunFile(string $file): bool
{
$this->phabelVar = 'phabelGeneratorYieldFrom'.\hash('sha256', $file);
return parent::shouldRunFile($file);
}
public function enterNode(YieldFrom $node, Context $ctx)
{
$var = new Variable($this->phabelVar.($this->phabelCount++));
$assign = new Assign($var, $node->expr);
$ifInstanceof = new If_(new Instanceof_(Plugin::callMethod($var, 'valid'), new FullyQualified(\YieldReturnValue::class)), ['stmts' => [new Assign()]]);
$while = new While_(new MethodCall($var, new Identifier('valid')));
foreach ($ctx->parents as $node) {
if ($node->hasAttribute('currentNodeIndex')) {
$ctx->insertBefore($node, $node->expr);
}
}
$generator = $node->expr;
return new Node\Expr\Yield_($generator);
}
}

View File

@ -35,15 +35,4 @@ class MultipleCatchReplacer extends Plugin
}
$node->catches = $catches;
}
/**
* Extends throwable replacer.
*
* @return string
*
* @psalm-return class-string
*/
public static function runWithBefore(): string
{
return [ThrowableReplacer::class];
}
}

View File

@ -5,7 +5,7 @@ namespace Spatie\Php7to5\NodeVisitors;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
class NullableTypeRemover extends Plugin
class NullableType extends Plugin
{
/**
* Remove nullable typehint.
@ -16,7 +16,7 @@ class NullableTypeRemover extends Plugin
{
return [
TypeHintStripper::class => [
'nulable' => true
'nullable' => true
]
];
}

View File

@ -0,0 +1,23 @@
<?php
namespace Spatie\Php7to5\NodeVisitors;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
class VoidReturnType extends Plugin
{
/**
* Remove void return typehint.
*
* @return array
*/
public static function runAfter(): array
{
return [
TypeHintStripper::class => [
'void' => true
]
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Phabel\Target\Php74;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
/**
* Turn an arrow function into a closure.
*/
class ArrowClosure
{
/**
* Enter arrow function.
*
* @param ArrowFunction $func Arrow function
*
* @return Closure
*/
public static function enterClosure(ArrowFunction $func): Closure
{
$nodes = [];
foreach ($func->getSubNodeNames() as $node) {
$nodes[$node] = $func->{$node};
}
return new Closure($nodes, $func->getAttributes());
}
}

27
src/Target/Php80.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace Phabel\Target;
use Phabel\Plugin;
use Phabel\UnionTypeStripper;
/**
* Makes changes necessary to polyfill PHP 8.0 and run on PHP 7.4 and below.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Php80 extends Plugin
{
public static function composerRequires(): array
{
return ['symfony/polyfill-php80' => '*'];
}
public static function runWithAfter(): array
{
return [
UnionTypeStripper::class,
];
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Phabel;
use Phabel\Plugin\TypeHintStripper;
/**
* Strip union types, polyfilling type checks.
*/
class UnionTypeStripper extends Plugin
{
/**
* Run with before.
*
* @return array
*/
public static function runWithAfter(): array
{
return [
TypeHintStripper::class => [
'union' => true
]
];
}
}

View File

@ -96,6 +96,7 @@ class Traverser
}
$ast = new RootNode($this->parser->parse(\file_get_contents($file)));
$context = new Context;
$context->parents->push($ast);
foreach ($reducedQueue as $queue) {
$this->traverseNode($ast, $queue, $context);
}
@ -106,7 +107,7 @@ class Traverser
* @param Node &$node Node
* @param SplQueue<Plugin> $plugins Plugins
* @param Context $context Context
*
*
* @return void
*/
public function traverseNode(Node &$node, SplQueue $plugins, Context $context): void
@ -134,10 +135,15 @@ class Traverser
$subNode = &$node->{$name};
if (\is_array($subNode)) {
foreach ($subNode as $index => &$subNodeNode) {
for ($index = 0; $index < \count($subNode);) {
$node->setAttribute('currentNodeIndex', $index);
$this->traverseNode($subNodeNode, $plugins, $context);
$index = $node->getAttribute('currentNodeIndex');
do {
$index++;
} while (\in_array($index, $node->getAttribute('skipNodes', [])));
}
$node->setAttribute('skipNodes', []);
} else {
$this->traverseNode($subNode, $plugins, $context);
}