diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php index f5d8e5c..c8ae481 100644 --- a/src/Composer/Plugin.php +++ b/src/Composer/Plugin.php @@ -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); } - } diff --git a/src/Context.php b/src/Context.php index 6b5a244..b9f5929 100644 --- a/src/Context.php +++ b/src/Context.php @@ -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. diff --git a/src/Plugin.php b/src/Plugin.php index 2efcfcb..974be55 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -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. * diff --git a/src/Plugin/Amp/YieldFromReplacer.php b/src/Plugin/Amp/YieldFromReplacer.php deleted file mode 100644 index 327fb9b..0000000 --- a/src/Plugin/Amp/YieldFromReplacer.php +++ /dev/null @@ -1,28 +0,0 @@ -expr; - - return new Node\Expr\Yield_($generator); - } -} diff --git a/src/Plugin/NameResolver.php b/src/Plugin/NameResolver.php new file mode 100644 index 0000000..dd16e75 --- /dev/null +++ b/src/Plugin/NameResolver.php @@ -0,0 +1,241 @@ +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 + ); + } +} diff --git a/src/Plugin/StringConcatOptimizer.php b/src/Plugin/StringConcatOptimizer.php new file mode 100644 index 0000000..b298650 --- /dev/null +++ b/src/Plugin/StringConcatOptimizer.php @@ -0,0 +1,58 @@ +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; + } +} diff --git a/src/Plugin/TypeHintStripper.php b/src/Plugin/TypeHintStripper.php index 7c4261a..5675762 100644 --- a/src/Plugin/TypeHintStripper.php +++ b/src/Plugin/TypeHintStripper.php @@ -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 + */ 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]; diff --git a/src/RootNode.php b/src/RootNode.php index fed6bc7..f1e3ac3 100644 --- a/src/RootNode.php +++ b/src/RootNode.php @@ -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'; } -} \ No newline at end of file +} diff --git a/src/Target/Php70/ThrowableReplacer.php b/src/Target/Php70/ThrowableReplacer.php index db56722..b7dd39f 100644 --- a/src/Target/Php70/ThrowableReplacer.php +++ b/src/Target/Php70/ThrowableReplacer.php @@ -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 => [] ]; } } diff --git a/src/Target/Php70/YieldFromReplacer.php b/src/Target/Php70/YieldFromReplacer.php new file mode 100644 index 0000000..d59e826 --- /dev/null +++ b/src/Target/Php70/YieldFromReplacer.php @@ -0,0 +1,42 @@ +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); + } +} diff --git a/src/Target/Php71/MultipleCatchReplacer.php b/src/Target/Php71/MultipleCatchReplacer.php index d134483..aa3b131 100644 --- a/src/Target/Php71/MultipleCatchReplacer.php +++ b/src/Target/Php71/MultipleCatchReplacer.php @@ -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]; - } } diff --git a/src/Target/Php71/NullableTypeRemover.php b/src/Target/Php71/NullableType.php similarity index 81% rename from src/Target/Php71/NullableTypeRemover.php rename to src/Target/Php71/NullableType.php index fe68fc9..791921c 100644 --- a/src/Target/Php71/NullableTypeRemover.php +++ b/src/Target/Php71/NullableType.php @@ -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 ] ]; } diff --git a/src/Target/Php71/VoidReturnType.php b/src/Target/Php71/VoidReturnType.php new file mode 100644 index 0000000..b26fae1 --- /dev/null +++ b/src/Target/Php71/VoidReturnType.php @@ -0,0 +1,23 @@ + [ + 'void' => true + ] + ]; + } +} diff --git a/src/Target/Php74/ArrowClosure.php b/src/Target/Php74/ArrowClosure.php new file mode 100644 index 0000000..ad53e32 --- /dev/null +++ b/src/Target/Php74/ArrowClosure.php @@ -0,0 +1,28 @@ +getSubNodeNames() as $node) { + $nodes[$node] = $func->{$node}; + } + return new Closure($nodes, $func->getAttributes()); + } +} diff --git a/src/Target/Php80.php b/src/Target/Php80.php new file mode 100644 index 0000000..eec270e --- /dev/null +++ b/src/Target/Php80.php @@ -0,0 +1,27 @@ + + * @license MIT + */ +class Php80 extends Plugin +{ + public static function composerRequires(): array + { + return ['symfony/polyfill-php80' => '*']; + } + + public static function runWithAfter(): array + { + return [ + UnionTypeStripper::class, + ]; + } +} diff --git a/src/Target/Php80/UnionTypeStripper.php b/src/Target/Php80/UnionTypeStripper.php new file mode 100644 index 0000000..93aeef6 --- /dev/null +++ b/src/Target/Php80/UnionTypeStripper.php @@ -0,0 +1,25 @@ + [ + 'union' => true + ] + ]; + } +} diff --git a/src/Traverser.php b/src/Traverser.php index fd35580..d61b9c6 100644 --- a/src/Traverser.php +++ b/src/Traverser.php @@ -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 $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); }