mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 21:38:22 +01:00
[TypeDeclaration] Add AddFunctionReturnTypeRector
This commit is contained in:
parent
548d13276e
commit
ca289def87
@ -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",
|
||||
|
@ -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: ~
|
||||
|
@ -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,
|
||||
|
134
packages/Php/src/Rector/Closure/ClosureToArrowFunctionRector.php
Normal file
134
packages/Php/src/Rector/Closure/ClosureToArrowFunctionRector.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Rector\Closure;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\ClosureUse;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see https://wiki.php.net/rfc/arrow_functions_v2
|
||||
*/
|
||||
final class ClosureToArrowFunctionRector extends AbstractRector
|
||||
{
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Change closure to arrow function', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function run($meetups)
|
||||
{
|
||||
return array_filter($meetups, function (Meetup $meetup) {
|
||||
return is_object($meetup);
|
||||
});
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function run($meetups)
|
||||
{
|
||||
return array_filter($meetups, fn(Meetup $meetup) => 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector;
|
||||
|
||||
use Rector\Php\Rector\Closure\ClosureToArrowFunctionRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class ClosureToArrowFunctionRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
public function run($meetups)
|
||||
{
|
||||
return array_filter($meetups, function (Meetup $meetup) {
|
||||
return is_object($meetup);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
public function run($meetups)
|
||||
{
|
||||
return array_filter($meetups, fn(Meetup $meetup) => is_object($meetup));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector\Fixture;
|
||||
|
||||
class ReferencedButNotUsed
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callback = function($b) use(&$i) {
|
||||
return ++$b;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector\Fixture;
|
||||
|
||||
class ReferencedButNotUsed
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callback = fn($b) => ++$b;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector\Fixture;
|
||||
|
||||
class SkipNoReturn
|
||||
{
|
||||
public function run($meetups)
|
||||
{
|
||||
return array_filter($meetups, function (Meetup $meetup) {
|
||||
is_object($meetup);
|
||||
});
|
||||
|
||||
return array_filter($meetups, function (Meetup $meetup) {
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Closure\ClosureToArrowFunctionRector\Fixture;
|
||||
|
||||
class SkipReferencedValue
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callback = function() use(&$i) {
|
||||
return ++$i;
|
||||
};
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Function_\ReservedFnFunctionRector;
|
||||
|
||||
use PhpParser\Parser\Tokens;
|
||||
use Rector\Php\Rector\Function_\ReservedFnFunctionRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
@ -9,6 +10,10 @@ final class ReservedFnFunctionRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
if (defined(Tokens::class . '::T_FN')) {
|
||||
$this->markTestSkipped('fn is reserved name in PHP 7.4');
|
||||
}
|
||||
|
||||
$this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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: ~
|
||||
|
Loading…
x
Reference in New Issue
Block a user