[TypeDeclaration] Add AddFunctionReturnTypeRector

This commit is contained in:
Tomas Votruba 2019-05-09 15:30:39 +02:00
parent 45940ade54
commit 83d2219ca7
55 changed files with 472 additions and 83 deletions

View File

@ -1,3 +1,4 @@
services: services:
Rector\Php\Rector\FunctionLike\ParamTypeDeclarationRector: ~ Rector\Php\Rector\FunctionLike\ParamTypeDeclarationRector: ~
Rector\Php\Rector\FunctionLike\ReturnTypeDeclarationRector: ~ Rector\Php\Rector\FunctionLike\ReturnTypeDeclarationRector: ~
Rector\TypeDeclaration\Rector\Function_\AddFunctionReturnTypeRector: ~

View File

@ -73,7 +73,7 @@ final class AttributeAwareNodeFactory
} }
if ($node instanceof PhpDocNode) { if ($node instanceof PhpDocNode) {
$this->nodeTraverser->traverseWithCallable($node, function (Node $node) { $this->nodeTraverser->traverseWithCallable($node, function (Node $node): AttributeAwareNodeInterface {
if ($node instanceof AttributeAwareNodeInterface) { if ($node instanceof AttributeAwareNodeInterface) {
return $node; return $node;
} }

View File

@ -32,7 +32,7 @@ final class StringsTypePhpDocNodeDecorator implements PhpDocNodeDecoratorInterfa
): AttributeAwarePhpDocNode { ): AttributeAwarePhpDocNode {
$this->nodeTraverser->traverseWithCallable( $this->nodeTraverser->traverseWithCallable(
$attributeAwarePhpDocNode, $attributeAwarePhpDocNode,
function (AttributeAwareNodeInterface $phpParserNode) { function (AttributeAwareNodeInterface $phpParserNode): AttributeAwareNodeInterface {
$typeNode = $this->resolveTypeNode($phpParserNode); $typeNode = $this->resolveTypeNode($phpParserNode);
if ($typeNode === null) { if ($typeNode === null) {
return $phpParserNode; return $phpParserNode;

View File

@ -84,7 +84,7 @@ CODE_SAMPLE
{ {
[$prefix, $table] = explode('.', $name->value); [$prefix, $table] = explode('.', $name->value);
$table = array_map( $table = array_map(
function ($token) { function ($token): string {
$tokens = explode('_', $token); $tokens = explode('_', $token);
return implode('', array_map('ucfirst', $tokens)); return implode('', array_map('ucfirst', $tokens));

View File

@ -53,7 +53,7 @@ final class SimplifyEmptyArrayCheckRector extends AbstractRector
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( $matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$node, $node,
// is_array(...) // is_array(...)
function (Node $node) { function (Node $node): bool {
return $node instanceof FuncCall && $this->isName($node, 'is_array'); return $node instanceof FuncCall && $this->isName($node, 'is_array');
}, },
Empty_::class Empty_::class

View File

@ -181,7 +181,7 @@ CODE_SAMPLE
return $this->binaryOpManipulator->matchFirstAndSecondConditionNode( return $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$binaryOp, $binaryOp,
Variable::class, Variable::class,
function (Node $node, Node $otherNode) use ($expr) { function (Node $node, Node $otherNode) use ($expr): bool {
return $this->areNodesEqual($otherNode, $expr); return $this->areNodesEqual($otherNode, $expr);
} }
); );

View File

@ -56,10 +56,10 @@ final class GetClassToInstanceOfRector extends AbstractRector
{ {
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( $matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$node, $node,
function (Node $node) { function (Node $node): bool {
return $this->isClassReference($node); return $this->isClassReference($node);
}, },
function (Node $node) { function (Node $node): bool {
return $this->isGetClassFuncCallNode($node); return $this->isGetClassFuncCallNode($node);
} }
); );

View File

@ -58,10 +58,10 @@ final class SimplifyArraySearchRector extends AbstractRector
{ {
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( $matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$node, $node,
function (Node $node) { function (Node $node): bool {
return $node instanceof FuncCall && $this->isName($node, 'array_search'); return $node instanceof FuncCall && $this->isName($node, 'array_search');
}, },
function (Node $node) { function (Node $node): bool {
return $this->isBool($node); return $this->isBool($node);
} }
); );

View File

@ -77,10 +77,10 @@ final class SimplifyConditionsRector extends AbstractRector
{ {
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( $matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$binaryOp, $binaryOp,
function (Node $binaryOp) { function (Node $binaryOp): bool {
return $binaryOp instanceof Identical || $binaryOp instanceof NotIdentical; return $binaryOp instanceof Identical || $binaryOp instanceof NotIdentical;
}, },
function (Node $binaryOp) { function (Node $binaryOp): bool {
return $this->isBool($binaryOp); return $this->isBool($binaryOp);
} }
); );

View File

@ -52,10 +52,10 @@ final class SimplifyTautologyTernaryRector extends AbstractRector
$isMatch = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( $isMatch = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$node->cond, $node->cond,
function (Node $leftNode) use ($node) { function (Node $leftNode) use ($node): bool {
return $this->areNodesEqual($leftNode, $node->if); return $this->areNodesEqual($leftNode, $node->if);
}, },
function (Node $leftNode) use ($node) { function (Node $leftNode) use ($node): bool {
return $this->areNodesEqual($leftNode, $node->else); return $this->areNodesEqual($leftNode, $node->else);
} }
); );

View File

@ -50,10 +50,10 @@ final class IdenticalFalseToBooleanNotRector extends AbstractRector
{ {
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode( $matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
$node, $node,
function (Node $node) { function (Node $node): bool {
return ! $node instanceof BinaryOp; return ! $node instanceof BinaryOp;
}, },
function (Node $node) { function (Node $node): bool {
return $this->isFalse($node); return $this->isFalse($node);
} }
); );

View File

@ -220,7 +220,7 @@ CODE_SAMPLE
$this->newUseStatements = []; $this->newUseStatements = [];
$this->newFunctionUseStatements = []; $this->newFunctionUseStatements = [];
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) { $this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node): ?Name {
if (! $node instanceof Name) { if (! $node instanceof Name) {
return null; return null;
} }
@ -277,6 +277,8 @@ CODE_SAMPLE
return new Name($shortName); return new Name($shortName);
} }
return null;
}); });
// for doc blocks // for doc blocks

View File

@ -34,7 +34,7 @@ final class DiffConsoleFormatter
private function formatWithTemplate(string $diff, string $template): string private function formatWithTemplate(string $diff, string $template): string
{ {
return sprintf($template, implode(PHP_EOL, array_map(function ($string) { return sprintf($template, implode(PHP_EOL, array_map(function ($string): string {
// make "+" lines green // make "+" lines green
$string = Strings::replace($string, '#^(\+.*)#', '<fg=green>$1</fg=green>'); $string = Strings::replace($string, '#^(\+.*)#', '<fg=green>$1</fg=green>');
// make "-" lines red // make "-" lines red

View File

@ -254,7 +254,7 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
{ {
foreach ($sections as $section) { foreach ($sections as $section) {
$pattern = '#("' . preg_quote($section, '#') . '": )\[(.*?)\](,)#ms'; $pattern = '#("' . preg_quote($section, '#') . '": )\[(.*?)\](,)#ms';
$jsonContent = Strings::replace($jsonContent, $pattern, function (array $match) { $jsonContent = Strings::replace($jsonContent, $pattern, function (array $match): string {
$inlined = Strings::replace($match[2], '#\s+#', ' '); $inlined = Strings::replace($match[2], '#\s+#', ' ');
$inlined = trim($inlined); $inlined = trim($inlined);
$inlined = '[' . $inlined . ']'; $inlined = '[' . $inlined . ']';

View File

@ -17,7 +17,7 @@ final class RectorsFinder
{ {
$allRectors = $this->findInDirectories([__DIR__ . '/../../../../packages', __DIR__ . '/../../../../src']); $allRectors = $this->findInDirectories([__DIR__ . '/../../../../packages', __DIR__ . '/../../../../src']);
return array_map(function (RectorInterface $rector) { return array_map(function (RectorInterface $rector): string {
return get_class($rector); return get_class($rector);
}, $allRectors); }, $allRectors);
} }

View File

@ -34,7 +34,7 @@ final class NodeInfoResult
*/ */
private function sortNodeInfosByClass(array $nodeInfos): array private function sortNodeInfosByClass(array $nodeInfos): array
{ {
usort($nodeInfos, function (NodeInfo $firstNodeInfo, NodeInfo $secondNodeInfo) { usort($nodeInfos, function (NodeInfo $firstNodeInfo, NodeInfo $secondNodeInfo): int {
return $firstNodeInfo->getClass() <=> $secondNodeInfo->getClass(); return $firstNodeInfo->getClass() <=> $secondNodeInfo->getClass();
}); });

View File

@ -99,7 +99,7 @@ CODE_SAMPLE
*/ */
private function resolveAssignedVariables(FunctionLike $functionLike): array private function resolveAssignedVariables(FunctionLike $functionLike): array
{ {
return $this->betterNodeFinder->find($functionLike, function (Node $node) { return $this->betterNodeFinder->find($functionLike, function (Node $node): bool {
if (! $node->getAttribute(AttributeKey::PARENT_NODE) instanceof Assign) { if (! $node->getAttribute(AttributeKey::PARENT_NODE) instanceof Assign) {
return false; return false;
} }
@ -130,7 +130,7 @@ CODE_SAMPLE
*/ */
private function resolveUsedVariables(Node $node, array $assignedVariables): array private function resolveUsedVariables(Node $node, array $assignedVariables): array
{ {
return $this->betterNodeFinder->find($node, function (Node $node) use ($assignedVariables) { return $this->betterNodeFinder->find($node, function (Node $node) use ($assignedVariables): bool {
if (! $node instanceof Variable) { if (! $node instanceof Variable) {
return false; return false;
} }
@ -216,7 +216,10 @@ CODE_SAMPLE
// sort // sort
usort( usort(
$nodesByTypeAndPosition, $nodesByTypeAndPosition,
function (VariableNodeUseInfo $firstVariableNodeUseInfo, VariableNodeUseInfo $secondVariableNodeUseInfo) { function (
VariableNodeUseInfo $firstVariableNodeUseInfo,
VariableNodeUseInfo $secondVariableNodeUseInfo
): int {
return $firstVariableNodeUseInfo->getStartTokenPosition() <=> $secondVariableNodeUseInfo->getStartTokenPosition(); return $firstVariableNodeUseInfo->getStartTokenPosition() <=> $secondVariableNodeUseInfo->getStartTokenPosition();
} }
); );
@ -285,7 +288,7 @@ CODE_SAMPLE
$isVariableAssigned = (bool) $this->betterNodeFinder->findFirst($assignNode->expr, function (Node $node) use ( $isVariableAssigned = (bool) $this->betterNodeFinder->findFirst($assignNode->expr, function (Node $node) use (
$nodeByTypeAndPosition $nodeByTypeAndPosition
) { ): bool {
return $this->areNodesEqual($node, $nodeByTypeAndPosition->getVariableNode()); return $this->areNodesEqual($node, $nodeByTypeAndPosition->getVariableNode());
}); });

View File

@ -209,7 +209,7 @@ CODE_SAMPLE
private function resolveAssignRouteNodes(ClassMethod $classMethod): array private function resolveAssignRouteNodes(ClassMethod $classMethod): array
{ {
// look for <...>[] = IRoute<Type> // look for <...>[] = IRoute<Type>
return $this->betterNodeFinder->find($classMethod->stmts, function (Node $classMethod) { return $this->betterNodeFinder->find($classMethod->stmts, function (Node $classMethod): bool {
if (! $classMethod instanceof Assign) { if (! $classMethod instanceof Assign) {
return false; return false;
} }

View File

@ -7,6 +7,7 @@ use PhpParser\Node\Identifier;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType; use PhpParser\Node\NullableType;
use Rector\Exception\ShouldNotHappenException;
use Rector\Php\TypeAnalyzer; use Rector\Php\TypeAnalyzer;
use Traversable; use Traversable;
@ -91,12 +92,14 @@ abstract class AbstractTypeInfo
*/ */
public function getTypeNode(bool $forceFqn = false) public function getTypeNode(bool $forceFqn = false)
{ {
$types = $forceFqn ? $this->fqnTypes : $this->types;
if (! $this->isTypehintAble()) { if (! $this->isTypehintAble()) {
return null; return null;
} }
$type = $types[0]; $type = $this->resolveTypeForTypehint($forceFqn);
if ($type === null) {
throw new ShouldNotHappenException();
}
if ($this->typeAnalyzer->isPhpReservedType($type)) { if ($this->typeAnalyzer->isPhpReservedType($type)) {
if ($this->isNullable) { if ($this->isNullable) {
@ -124,8 +127,12 @@ abstract class AbstractTypeInfo
return false; return false;
} }
$typeCount = count($this->types); // are object subtypes
if ($this->areMutualObjectSubtypes($this->types)) {
return true;
}
$typeCount = count($this->types);
if ($typeCount >= 2 && $this->isArraySubtype($this->types)) { if ($typeCount >= 2 && $this->isArraySubtype($this->types)) {
return true; return true;
} }
@ -293,4 +300,50 @@ abstract class AbstractTypeInfo
return $types === $arraySubtypeGroup; return $types === $arraySubtypeGroup;
} }
/**
* @param string[] $types
*/
private function areMutualObjectSubtypes(array $types): bool
{
return $this->resolveMutualObjectSubtype($types) !== null;
}
/**
* @param string[] $types
*/
private function resolveMutualObjectSubtype(array $types): ?string
{
foreach ($types as $type) {
if ($this->classLikeExists($type)) {
return null;
}
foreach ($types as $subloopType) {
if (! is_a($subloopType, $type, true)) {
continue 2;
}
}
return $type;
}
return null;
}
private function resolveTypeForTypehint(bool $forceFqn): ?string
{
if ($this->areMutualObjectSubtypes($this->types)) {
return $this->resolveMutualObjectSubtype($this->types);
}
$types = $forceFqn ? $this->fqnTypes : $this->types;
return $types[0];
}
private function classLikeExists(string $type): bool
{
return ! class_exists($type) && ! interface_exists($type) && ! trait_exists($type);
}
} }

View File

@ -410,7 +410,9 @@ final class DocBlockManipulator
$phpDocInfo = $this->createPhpDocInfoFromNode($node); $phpDocInfo = $this->createPhpDocInfoFromNode($node);
$phpDocNode = $phpDocInfo->getPhpDocNode(); $phpDocNode = $phpDocInfo->getPhpDocNode();
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) { $this->nodeTraverser->traverseWithCallable($phpDocNode, function (
AttributeAwareNodeInterface $node
): AttributeAwareNodeInterface {
if (! $node instanceof IdentifierTypeNode) { if (! $node instanceof IdentifierTypeNode) {
return $node; return $node;
} }
@ -447,7 +449,7 @@ final class DocBlockManipulator
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) use ( $this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) use (
$namespacePrefix, $namespacePrefix,
$excludedClasses $excludedClasses
) { ): AttributeAwareNodeInterface {
if (! $node instanceof IdentifierTypeNode) { if (! $node instanceof IdentifierTypeNode) {
return $node; return $node;
} }

View File

@ -54,7 +54,7 @@ final class FqnNamePhpDocNodeDecorator implements PhpDocNodeDecoratorInterface
{ {
$this->nodeTraverser->traverseWithCallable( $this->nodeTraverser->traverseWithCallable(
$attributeAwarePhpDocNode, $attributeAwarePhpDocNode,
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) use ($node) { function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) use ($node): AttributeAwareNodeInterface {
if (! $attributeAwarePhpDocNode instanceof IdentifierTypeNode) { if (! $attributeAwarePhpDocNode instanceof IdentifierTypeNode) {
return $attributeAwarePhpDocNode; return $attributeAwarePhpDocNode;
} }
@ -77,7 +77,7 @@ final class FqnNamePhpDocNodeDecorator implements PhpDocNodeDecoratorInterface
// collect to particular node types // collect to particular node types
$this->nodeTraverser->traverseWithCallable( $this->nodeTraverser->traverseWithCallable(
$attributeAwarePhpDocNode, $attributeAwarePhpDocNode,
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) { function (AttributeAwareNodeInterface $attributeAwarePhpDocNode): AttributeAwareNodeInterface {
if (! $this->isTypeAwareNode($attributeAwarePhpDocNode)) { if (! $this->isTypeAwareNode($attributeAwarePhpDocNode)) {
return $attributeAwarePhpDocNode; return $attributeAwarePhpDocNode;
} }

View File

@ -152,7 +152,7 @@ CODE_SAMPLE
return true; return true;
} }
$variableAssign = $this->betterNodeFinder->findFirstPrevious($assign, function (Node $node) use ($expr) { $variableAssign = $this->betterNodeFinder->findFirstPrevious($assign, function (Node $node) use ($expr): bool {
if (! $node instanceof Assign) { if (! $node instanceof Assign) {
return false; return false;
} }

View File

@ -88,7 +88,7 @@ CODE_SAMPLE
/** @var FuncCall|null $keyFuncCall */ /** @var FuncCall|null $keyFuncCall */
$keyFuncCall = $this->betterNodeFinder->findFirst($nextExpression, function (Node $node) use ( $keyFuncCall = $this->betterNodeFinder->findFirst($nextExpression, function (Node $node) use (
$resetOrEndFuncCall $resetOrEndFuncCall
) { ): bool {
if (! $node instanceof FuncCall) { if (! $node instanceof FuncCall) {
return false; return false;
} }

View File

@ -79,7 +79,7 @@ CODE_SAMPLE
$this->callableNodeTraverser->traverseNodesWithCallable([$nextExpression], function (Node $node) use ( $this->callableNodeTraverser->traverseNodesWithCallable([$nextExpression], function (Node $node) use (
$resultVariable $resultVariable
) { ): ?Variable {
if ($node instanceof FuncCall) { if ($node instanceof FuncCall) {
if ($this->isName($node, 'get_defined_vars')) { if ($this->isName($node, 'get_defined_vars')) {
return $resultVariable; return $resultVariable;

View File

@ -129,7 +129,7 @@ CODE_SAMPLE
$stmt = $contentNodes[0]->expr; $stmt = $contentNodes[0]->expr;
$this->callableNodeTraverser->traverseNodesWithCallable([$stmt], function (Node $node) { $this->callableNodeTraverser->traverseNodesWithCallable([$stmt], function (Node $node): Node {
if (! $node instanceof String_) { if (! $node instanceof String_) {
return $node; return $node;
} }

View File

@ -36,7 +36,7 @@ final class LetManipulator
$hasBeConstructedThrough = (bool) $this->betterNodeFinder->find( $hasBeConstructedThrough = (bool) $this->betterNodeFinder->find(
(array) $method->stmts, (array) $method->stmts,
function (Node $node) { function (Node $node): ?bool {
if (! $node instanceof MethodCall) { if (! $node instanceof MethodCall) {
return null; return null;
} }

View File

@ -146,7 +146,7 @@ CODE_SAMPLE
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) use ( $this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) use (
$classesUsingTypes $classesUsingTypes
) { ): ?MethodCall {
if (! $node instanceof New_) { if (! $node instanceof New_) {
return null; return null;
} }

View File

@ -137,7 +137,7 @@ CODE_SAMPLE
foreach ($this->staticTypes as $implements => $staticType) { foreach ($this->staticTypes as $implements => $staticType) {
$containsEntityFactoryStaticCall = (bool) $this->betterNodeFinder->findFirst( $containsEntityFactoryStaticCall = (bool) $this->betterNodeFinder->findFirst(
$class->stmts, $class->stmts,
function (Node $node) use ($staticType) { function (Node $node) use ($staticType): bool {
return $this->isEntityFactoryStaticCall($node, $staticType); return $this->isEntityFactoryStaticCall($node, $staticType);
} }
); );

View File

@ -148,7 +148,7 @@ CODE_SAMPLE
} }
// "$this->getRequest()" // "$this->getRequest()"
$isGetRequestMethod = (bool) $this->betterNodeFinder->find($node, function (Node $node) { $isGetRequestMethod = (bool) $this->betterNodeFinder->find($node, function (Node $node): bool {
return $this->isName($node, 'getRequest'); return $this->isName($node, 'getRequest');
}); });
@ -158,7 +158,7 @@ CODE_SAMPLE
// "$this->get('request')" // "$this->get('request')"
/** @var MethodCall[] $getMethodCalls */ /** @var MethodCall[] $getMethodCalls */
$getMethodCalls = $this->betterNodeFinder->find($node, function (Node $node) { $getMethodCalls = $this->betterNodeFinder->find($node, function (Node $node): bool {
if (! $node instanceof MethodCall) { if (! $node instanceof MethodCall) {
return false; return false;
} }

View File

@ -103,7 +103,7 @@ CODE_SAMPLE
return null; return null;
} }
return $this->betterNodeFinder->findFirst([$nextExpression], function (Node $node) { return $this->betterNodeFinder->findFirst([$nextExpression], function (Node $node): bool {
if (! $node instanceof MethodCall) { if (! $node instanceof MethodCall) {
return false; return false;
} }

View File

@ -155,7 +155,9 @@ CODE_SAMPLE
private function findPreviousNodeAssign(Node $node, Node $firstArgument): ?Assign private function findPreviousNodeAssign(Node $node, Node $firstArgument): ?Assign
{ {
return $this->betterNodeFinder->findFirstPrevious($node, function (Node $checkedNode) use ($firstArgument) { return $this->betterNodeFinder->findFirstPrevious($node, function (Node $checkedNode) use (
$firstArgument
): ?Assign {
if (! $checkedNode instanceof Assign) { if (! $checkedNode instanceof Assign) {
return null; return null;
} }

View File

@ -132,7 +132,7 @@ CODE_SAMPLE
return null; return null;
} }
$this->callableNodeTraverser->traverseNodesWithCallable([$node->expr], function (Node $node) { $this->callableNodeTraverser->traverseNodesWithCallable([$node->expr], function (Node $node): ?Node {
if (! $node instanceof ArrayItem) { if (! $node instanceof ArrayItem) {
return null; return null;
} }

View File

@ -0,0 +1,97 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\Rector\Closure;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpParser\Node\Manipulator\FunctionLikeManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class AddClosureReturnTypeRector extends AbstractRector
{
/**
* @var FunctionLikeManipulator
*/
private $functionLikeManipulator;
public function __construct(FunctionLikeManipulator $functionLikeManipulator)
{
$this->functionLikeManipulator = $functionLikeManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Add known return type to functions', [
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, function (Meetup $meetup): bool {
return is_object($meetup);
});
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Node\Expr\Closure::class];
}
/**
* @param Node\Expr\Closure $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isAtLeastPhpVersion('7.0')) {
return null;
}
if ($node->returnType) {
return null;
}
/** @var Scope|null $scope */
$scope = $node->getAttribute(AttributeKey::SCOPE);
if ($scope === null) {
return null;
}
$staticReturnType = $this->functionLikeManipulator->resolveStaticReturnTypeInfo($node);
if ($staticReturnType === null) {
return null;
}
$returnTypeNode = $staticReturnType->getTypeNode();
if ($returnTypeNode === null) {
return null;
}
$node->returnType = $returnTypeNode;
return $node;
}
}

View File

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\Closure\AddClosureReturnTypeRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector;
final class AddClosureReturnTypeRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/return_type_object.php.inc',
__DIR__ . '/Fixture/callable_false_positive.php.inc',
__DIR__ . '/Fixture/subtype_of_object.php.inc',
]);
}
protected function getRectorClass(): string
{
return AddClosureReturnTypeRector::class;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
class CallableFalsePositive
{
/**
* @param callable[]|string[] $callables
* @return callable[]
*/
public function populate(array $callables): array
{
$populatedCallables = [];
foreach ($callables as $key => $callable) {
// 1. convert instant assign to callable
if (!is_callable($callable)) {
$populatedCallables[$key] = function () use ($callable) {
return $callable;
};
continue;
}
}
return $populatedCallables;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
class CallableFalsePositive
{
/**
* @param callable[]|string[] $callables
* @return callable[]
*/
public function populate(array $callables): array
{
$populatedCallables = [];
foreach ($callables as $key => $callable) {
// 1. convert instant assign to callable
if (!is_callable($callable)) {
$populatedCallables[$key] = function () use ($callable) : string {
return $callable;
};
continue;
}
}
return $populatedCallables;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
class SomeClass
{
public function run($meetups)
{
return array_filter($meetups, function ($meetup) {
return is_object($meetup);
});
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
class SomeClass
{
public function run($meetups)
{
return array_filter($meetups, function ($meetup) : bool {
return is_object($meetup);
});
}
}
?>

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
use PhpParser\Node\Stmt\Class_;
class ReturnTypeObject
{
public function shouldSkip()
{
$nonAnonymousClassNodes = array_filter([], function (Class_ $classNode) {
return $classNode->name;
});
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
use PhpParser\Node\Stmt\Class_;
class ReturnTypeObject
{
public function shouldSkip()
{
$nonAnonymousClassNodes = array_filter([], function (Class_ $classNode) : ?\PhpParser\Node\Identifier {
return $classNode->name;
});
}
}
?>

View File

@ -0,0 +1,69 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
class SubtypeOfObject
{
public function run($stmt)
{
$this->callableNodeTraverser->traverseNodesWithCallable([$stmt], function (Node $node)
{
if (!$node instanceof String_) {
return $node;
}
$match = Strings::match($node->value, '#(\\$|\\\\)(?<number>\d+)#');
if (!$match) {
return $node;
}
$matchesVariable = new Variable('matches');
return new ArrayDimFetch($matchesVariable, new LNumber((int)$match['number']));
});
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\Function_\AddClosureReturnTypeRector\Fixture;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
class SubtypeOfObject
{
public function run($stmt)
{
$this->callableNodeTraverser->traverseNodesWithCallable([$stmt], function (Node $node) : \PhpParser\Node
{
if (!$node instanceof String_) {
return $node;
}
$match = Strings::match($node->value, '#(\\$|\\\\)(?<number>\d+)#');
if (!$match) {
return $node;
}
$matchesVariable = new Variable('matches');
return new ArrayDimFetch($matchesVariable, new LNumber((int)$match['number']));
});
}
}
?>

View File

@ -155,3 +155,6 @@ parameters:
- '#Empty array passed to foreach#' - '#Empty array passed to foreach#'
- '#Method Rector\\RemovingStatic\\UniqueObjectFactoryFactory\:\:resolveClassShortName\(\) should return string but returns string\|false#' - '#Method Rector\\RemovingStatic\\UniqueObjectFactoryFactory\:\:resolveClassShortName\(\) should return string but returns string\|false#'
- '#Strict comparison using \=\=\= between PhpParser\\Node\\Expr\\ArrayItem and null will always evaluate to false#' - '#Strict comparison using \=\=\= between PhpParser\\Node\\Expr\\ArrayItem and null will always evaluate to false#'
- '#Anonymous function should have native typehint "string"#'
- '#Parameter \#2 \.\.\.\$args of function array_merge expects array, array<int, string\>\|false given#'
- '#Method Rector\\Collector\\CallableCollectorPopulator\:\:populate\(\) should return array<Closure\> but returns array<int\|string, callable\>#'

View File

@ -15,4 +15,5 @@ parameters:
php_version_features: '7.1' php_version_features: '7.1'
services: services:
Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~ # Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector: ~

View File

@ -9,8 +9,8 @@ use ReflectionFunction;
final class CallableCollectorPopulator final class CallableCollectorPopulator
{ {
/** /**
* @param string[]|callable[]|Closure[] $callables * @param string[]|Closure[]|mixed[] $callables
* @return callable[] * @return Closure[]
*/ */
public function populate(array $callables): array public function populate(array $callables): array
{ {

View File

@ -124,7 +124,7 @@ final class FilesFinder
return; return;
} }
$finder->filter(function (SplFileInfo $splFileInfo) { $finder->filter(function (SplFileInfo $splFileInfo): bool {
// return false to remove file // return false to remove file
foreach ($this->excludePaths as $excludePath) { foreach ($this->excludePaths as $excludePath) {
if (Strings::match($splFileInfo->getRealPath(), '#' . preg_quote($excludePath, '#') . '#')) { if (Strings::match($splFileInfo->getRealPath(), '#' . preg_quote($excludePath, '#') . '#')) {

View File

@ -185,7 +185,7 @@ final class RegexPatternArgumentManipulator
} }
/** @var Assign[] $assignNode */ /** @var Assign[] $assignNode */
return $this->betterNodeFinder->find([$methodNode], function (Node $node) use ($variable) { return $this->betterNodeFinder->find([$methodNode], function (Node $node) use ($variable): ?Assign {
if (! $node instanceof Assign) { if (! $node instanceof Assign) {
return null; return null;
} }

View File

@ -149,7 +149,7 @@ final class BetterNodeFinder
{ {
$assignNodes = $this->findInstanceOf($node, Assign::class); $assignNodes = $this->findInstanceOf($node, Assign::class);
return array_filter($assignNodes, function (Assign $assign) use ($variable) { return array_filter($assignNodes, function (Assign $assign) use ($variable): bool {
if ($this->betterStandardPrinter->areNodesEqual($assign->var, $variable)) { if ($this->betterStandardPrinter->areNodesEqual($assign->var, $variable)) {
return true; return true;
} }

View File

@ -63,7 +63,7 @@ final class BinaryOpManipulator
return $condition; return $condition;
} }
return function (Node $node) use ($condition) { return function (Node $node) use ($condition): bool {
return is_a($node, $condition, true); return is_a($node, $condition, true);
}; };
} }

View File

@ -100,7 +100,7 @@ final class CallManipulator
private function containsFuncGetArgsFuncCall(Node $node): bool private function containsFuncGetArgsFuncCall(Node $node): bool
{ {
return (bool) $this->betterNodeFinder->findFirst($node, function (Node $node) { return (bool) $this->betterNodeFinder->findFirst($node, function (Node $node): ?bool {
if (! $node instanceof FuncCall) { if (! $node instanceof FuncCall) {
return null; return null;
} }
@ -134,7 +134,7 @@ final class CallManipulator
$externalFunctionNode = $this->betterNodeFinder->findFirst($externalFileContent, function (Node $node) use ( $externalFunctionNode = $this->betterNodeFinder->findFirst($externalFileContent, function (Node $node) use (
$requiredExternalType, $requiredExternalType,
$functionName $functionName
) { ): ?bool {
if (! is_a($node, $requiredExternalType, true)) { if (! is_a($node, $requiredExternalType, true)) {
return null; return null;
} }

View File

@ -47,7 +47,7 @@ final class ClassConstManipulator
return []; return [];
} }
return $this->betterNodeFinder->find($classNode, function (Node $node) use ($classConst) { return $this->betterNodeFinder->find($classNode, function (Node $node) use ($classConst): bool {
// itself // itself
if ($this->betterStandardPrinter->areNodesEqual($node, $classConst)) { if ($this->betterStandardPrinter->areNodesEqual($node, $classConst)) {
return false; return false;

View File

@ -236,7 +236,7 @@ final class ClassManipulator
*/ */
public function getMethods(Class_ $class): array public function getMethods(Class_ $class): array
{ {
return array_filter($class->stmts, function (Node $node) { return array_filter($class->stmts, function (Node $node): bool {
return $node instanceof ClassMethod; return $node instanceof ClassMethod;
}); });
} }

View File

@ -68,7 +68,7 @@ final class ClassMethodManipulator
{ {
$isUsedDirectly = (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( $isUsedDirectly = (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use (
$param $param
) { ): bool {
return $this->betterStandardPrinter->areNodesEqual($node, $param->var); return $this->betterStandardPrinter->areNodesEqual($node, $param->var);
}); });
@ -77,7 +77,7 @@ final class ClassMethodManipulator
} }
/** @var FuncCall[] $compactFuncCalls */ /** @var FuncCall[] $compactFuncCalls */
$compactFuncCalls = $this->betterNodeFinder->find((array) $classMethod->stmts, function (Node $node) { $compactFuncCalls = $this->betterNodeFinder->find((array) $classMethod->stmts, function (Node $node): bool {
if (! $node instanceof FuncCall) { if (! $node instanceof FuncCall) {
return false; return false;
} }
@ -152,7 +152,7 @@ final class ClassMethodManipulator
*/ */
public function isStaticClassMethod(ClassMethod $classMethod): bool public function isStaticClassMethod(ClassMethod $classMethod): bool
{ {
return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) { return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool {
if (! $node instanceof Variable) { if (! $node instanceof Variable) {
return false; return false;
} }

View File

@ -2,6 +2,7 @@
namespace Rector\PhpParser\Node\Manipulator; namespace Rector\PhpParser\Node\Manipulator;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike; use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Function_;
@ -42,7 +43,7 @@ final class FunctionLikeManipulator
/** /**
* Based on static analysis of code, looking for return types * Based on static analysis of code, looking for return types
* @param ClassMethod|Function_ $functionLike * @param ClassMethod|Function_|Closure $functionLike
*/ */
public function resolveStaticReturnTypeInfo(FunctionLike $functionLike): ?ReturnTypeInfo public function resolveStaticReturnTypeInfo(FunctionLike $functionLike): ?ReturnTypeInfo
{ {

View File

@ -82,7 +82,7 @@ final class ValueResolver
return $this->constExprEvaluator; return $this->constExprEvaluator;
} }
$this->constExprEvaluator = new ConstExprEvaluator(function (Expr $expr) { $this->constExprEvaluator = new ConstExprEvaluator(function (Expr $expr): ?string {
if ($expr instanceof Dir) { if ($expr instanceof Dir) {
// __DIR__ // __DIR__
return $this->resolveDirConstant($expr); return $this->resolveDirConstant($expr);

View File

@ -77,7 +77,7 @@ trait BetterStandardPrinterTrait
*/ */
protected function isNodeUsedIn(Node $seekedNode, $nodes): bool protected function isNodeUsedIn(Node $seekedNode, $nodes): bool
{ {
return (bool) $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($seekedNode) { return (bool) $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($seekedNode): bool {
return $this->areNodesEqual($node, $seekedNode); return $this->areNodesEqual($node, $seekedNode);
}); });
} }

View File

@ -38,6 +38,7 @@ final class RenameClassRector extends AbstractRector
* @var string[] * @var string[]
*/ */
private $alreadyProcessedClasses = []; private $alreadyProcessedClasses = [];
/** /**
* @var ClassNaming * @var ClassNaming
*/ */
@ -50,8 +51,7 @@ final class RenameClassRector extends AbstractRector
DocBlockManipulator $docBlockManipulator, DocBlockManipulator $docBlockManipulator,
ClassNaming $classNaming, ClassNaming $classNaming,
array $oldToNewClasses = [] array $oldToNewClasses = []
) ) {
{
$this->docBlockManipulator = $docBlockManipulator; $this->docBlockManipulator = $docBlockManipulator;
$this->classNaming = $classNaming; $this->classNaming = $classNaming;
$this->oldToNewClasses = $oldToNewClasses; $this->oldToNewClasses = $oldToNewClasses;
@ -120,21 +120,7 @@ CODE_SAMPLE
} }
if ($node instanceof Name) { if ($node instanceof Name) {
$name = $this->getName($node); return $this->refactorName($node);
if ($name === null) {
return null;
}
$newName = $this->oldToNewClasses[$name] ?? null;
if (! $newName) {
return null;
}
if (! $this->isClassToInterfaceValidChange($node, $newName)) {
return null;
}
return new FullyQualified($newName);
} }
if ($node instanceof Namespace_) { if ($node instanceof Namespace_) {
@ -145,7 +131,7 @@ CODE_SAMPLE
$node = $this->refactorClassLikeNode($node); $node = $this->refactorClassLikeNode($node);
} }
if (! $node) { if ($node === null) {
return null; return null;
} }
@ -222,7 +208,7 @@ CODE_SAMPLE
} }
$classNode = $this->getClassOfNamespaceToRefactor($node); $classNode = $this->getClassOfNamespaceToRefactor($node);
if (! $classNode) { if ($classNode === null) {
return null; return null;
} }
@ -243,7 +229,7 @@ CODE_SAMPLE
private function getClassOfNamespaceToRefactor(Namespace_ $namespace): ?ClassLike private function getClassOfNamespaceToRefactor(Namespace_ $namespace): ?ClassLike
{ {
$foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node) { $foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node): bool {
if (! $node instanceof ClassLike) { if (! $node instanceof ClassLike) {
return false; return false;
} }
@ -286,4 +272,23 @@ CODE_SAMPLE
return $classLike; return $classLike;
} }
private function refactorName(Node $node): ?FullyQualified
{
$name = $this->getName($node);
if ($name === null) {
return null;
}
$newName = $this->oldToNewClasses[$name] ?? null;
if (! $newName) {
return null;
}
if (! $this->isClassToInterfaceValidChange($node, $newName)) {
return null;
}
return new FullyQualified($newName);
}
} }

View File

@ -4,6 +4,7 @@ namespace Rector\Rector\Psr4;
use Nette\Utils\FileSystem; use Nette\Utils\FileSystem;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Namespace_;
use Rector\FileSystemRector\Rector\AbstractFileSystemRector; use Rector\FileSystemRector\Rector\AbstractFileSystemRector;
@ -123,7 +124,7 @@ CODE_SAMPLE
/** @var Class_[] $classNodes */ /** @var Class_[] $classNodes */
$classNodes = $this->betterNodeFinder->findInstanceOf($nodes, Class_::class); $classNodes = $this->betterNodeFinder->findInstanceOf($nodes, Class_::class);
$nonAnonymousClassNodes = array_filter($classNodes, function (Class_ $classNode) { $nonAnonymousClassNodes = array_filter($classNodes, function (Class_ $classNode): ?Identifier {
return $classNode->name; return $classNode->name;
}); });

View File

@ -41,7 +41,7 @@ final class FunctionReflectionResolver
$nodes = $this->parser->parseFile($stubFileLocation); $nodes = $this->parser->parseFile($stubFileLocation);
/** @var Function_|null $function */ /** @var Function_|null $function */
$function = $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($nodeName) { $function = $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($nodeName): bool {
if (! $node instanceof Function_) { if (! $node instanceof Function_) {
return false; return false;
} }