diff --git a/composer.json b/composer.json index 9677be76064..c90a9e1e1aa 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "jetbrains/phpstorm-stubs": "^2019.1", "nette/robot-loader": "^3.1", "nette/utils": "^2.5|^3.0", - "nikic/php-parser": "^4.2.1", + "nikic/php-parser": "dev-master", "phpstan/phpdoc-parser": "0.3.3", "phpstan/phpstan": "0.11.6", "sebastian/diff": "^3.0", diff --git a/config/level/php/php74.yaml b/config/level/php/php74.yaml index 45dd834027a..bed9966e554 100644 --- a/config/level/php/php74.yaml +++ b/config/level/php/php74.yaml @@ -16,3 +16,4 @@ services: Rector\Php\Rector\Double\RealToFloatTypeCastRector: ~ Rector\Php\Rector\Assign\NullCoalescingOperatorRector: ~ Rector\Php\Rector\Function_\ReservedFnFunctionRector: ~ + Rector\Php\Rector\Closure\ClosureToArrowFunctionRector: ~ diff --git a/packages/ContributorTools/src/Command/DumpNodesCommand.php b/packages/ContributorTools/src/Command/DumpNodesCommand.php index caaec8ba0b3..dc973101eee 100644 --- a/packages/ContributorTools/src/Command/DumpNodesCommand.php +++ b/packages/ContributorTools/src/Command/DumpNodesCommand.php @@ -176,6 +176,11 @@ final class DumpNodesCommand extends AbstractCommand if ($contructorReflection->getNumberOfRequiredParameters() === 0) { $node = $nodeClassReflection->newInstance(); + // special case + if ($node instanceof Node\Expr\ArrowFunction) { + $node->expr = new LNumber(1); + } + $category = $this->resolveCategoryByNodeClass($nodeClass); $this->nodeInfoResult->addNodeInfo($category, new NodeInfo( $nodeClass, diff --git a/packages/Php/src/Rector/Closure/ClosureToArrowFunctionRector.php b/packages/Php/src/Rector/Closure/ClosureToArrowFunctionRector.php new file mode 100644 index 00000000000..9403d4a2b34 --- /dev/null +++ b/packages/Php/src/Rector/Closure/ClosureToArrowFunctionRector.php @@ -0,0 +1,134 @@ + is_object($meetup)); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Closure::class]; + } + + /** + * @param Closure $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isAtLeastPhpVersion('7.4')) { + return null; + } + + if (count((array) $node->stmts) !== 1) { + return null; + } + + if (! $node->stmts[0] instanceof Return_) { + return null; + } + + /** @var Return_ $return */ + $return = $node->stmts[0]; + if ($return->expr === null) { + return null; + } + + if ($this->shouldSkipForUsedReferencedValue($node, $return)) { + return null; + } + + $arrowFunction = new ArrowFunction(); + $arrowFunction->params = $node->params; + $arrowFunction->returnType = $node->returnType; + $arrowFunction->byRef = $node->byRef; + + $arrowFunction->expr = $return->expr; + + return $arrowFunction; + } + + private function shouldSkipForUsedReferencedValue(Closure $closure, Return_ $return): bool + { + if ($return->expr === null) { + return false; + } + + $referencedValues = $this->resolveReferencedUseVariablesFromClosure($closure); + if ($referencedValues === []) { + return false; + } + + return (bool) $this->betterNodeFinder->findFirst([$return->expr], function (Node $node) use ( + $referencedValues + ): bool { + foreach ($referencedValues as $referencedValue) { + if ($this->areNodesEqual($node, $referencedValue)) { + return true; + } + } + + return false; + }); + } + + /** + * @return Variable[] + */ + private function resolveReferencedUseVariablesFromClosure(Closure $closure): array + { + $referencedValues = []; + + /** @var ClosureUse $use */ + foreach ((array) $closure->uses as $use) { + if ($use->byRef) { + $referencedValues[] = $use->var; + } + } + + return $referencedValues; + } +} diff --git a/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/ClosureToArrowFunctionRectorTest.php b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/ClosureToArrowFunctionRectorTest.php new file mode 100644 index 00000000000..7735ac97ca7 --- /dev/null +++ b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/ClosureToArrowFunctionRectorTest.php @@ -0,0 +1,25 @@ +doTestFiles([ + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/referenced_but_not_used.php.inc', + // skip + __DIR__ . '/Fixture/skip_no_return.php.inc', + __DIR__ . '/Fixture/skip_referenced_value.php.inc', + ]); + } + + protected function getRectorClass(): string + { + return ClosureToArrowFunctionRector::class; + } +} diff --git a/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/fixture.php.inc b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..521aaaf409f --- /dev/null +++ b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/fixture.php.inc @@ -0,0 +1,29 @@ + +----- + is_object($meetup)); + } +} + +?> diff --git a/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/referenced_but_not_used.php.inc b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/referenced_but_not_used.php.inc new file mode 100644 index 00000000000..23c1085fb39 --- /dev/null +++ b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/referenced_but_not_used.php.inc @@ -0,0 +1,29 @@ + +----- + ++$b; + } +} + +?> diff --git a/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_no_return.php.inc b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_no_return.php.inc new file mode 100644 index 00000000000..6daa976b424 --- /dev/null +++ b/packages/Php/tests/Rector/Closure/ClosureToArrowFunctionRector/Fixture/skip_no_return.php.inc @@ -0,0 +1,17 @@ +markTestSkipped('fn is reserved name in PHP 7.4'); + } + $this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']); } diff --git a/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php b/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php index d62220a2f90..ef6655568c4 100644 --- a/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php +++ b/packages/TypeDeclaration/src/Rector/Closure/AddClosureReturnTypeRector.php @@ -3,6 +3,7 @@ namespace Rector\TypeDeclaration\Rector\Closure; use PhpParser\Node; +use PhpParser\Node\Expr\Closure; use PHPStan\Analyser\Scope; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\Manipulator\FunctionLikeManipulator; @@ -58,11 +59,11 @@ CODE_SAMPLE */ public function getNodeTypes(): array { - return [Node\Expr\Closure::class]; + return [Closure::class]; } /** - * @param Node\Expr\Closure $node + * @param Closure $node */ public function refactor(Node $node): ?Node { diff --git a/rector.yaml b/rector.yaml index 11f302f878b..6eec4b922f2 100644 --- a/rector.yaml +++ b/rector.yaml @@ -15,5 +15,5 @@ parameters: php_version_features: '7.1' services: -# Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~ - Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector: ~ + Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~ +# Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector: ~