mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-18 05:48:21 +01:00
[TypeDeclaration] Add AddFunctionReturnTypeRector
This commit is contained in:
parent
45940ade54
commit
83d2219ca7
@ -1,3 +1,4 @@
|
||||
services:
|
||||
Rector\Php\Rector\FunctionLike\ParamTypeDeclarationRector: ~
|
||||
Rector\Php\Rector\FunctionLike\ReturnTypeDeclarationRector: ~
|
||||
Rector\TypeDeclaration\Rector\Function_\AddFunctionReturnTypeRector: ~
|
||||
|
@ -73,7 +73,7 @@ final class AttributeAwareNodeFactory
|
||||
}
|
||||
|
||||
if ($node instanceof PhpDocNode) {
|
||||
$this->nodeTraverser->traverseWithCallable($node, function (Node $node) {
|
||||
$this->nodeTraverser->traverseWithCallable($node, function (Node $node): AttributeAwareNodeInterface {
|
||||
if ($node instanceof AttributeAwareNodeInterface) {
|
||||
return $node;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ final class StringsTypePhpDocNodeDecorator implements PhpDocNodeDecoratorInterfa
|
||||
): AttributeAwarePhpDocNode {
|
||||
$this->nodeTraverser->traverseWithCallable(
|
||||
$attributeAwarePhpDocNode,
|
||||
function (AttributeAwareNodeInterface $phpParserNode) {
|
||||
function (AttributeAwareNodeInterface $phpParserNode): AttributeAwareNodeInterface {
|
||||
$typeNode = $this->resolveTypeNode($phpParserNode);
|
||||
if ($typeNode === null) {
|
||||
return $phpParserNode;
|
||||
|
@ -84,7 +84,7 @@ CODE_SAMPLE
|
||||
{
|
||||
[$prefix, $table] = explode('.', $name->value);
|
||||
$table = array_map(
|
||||
function ($token) {
|
||||
function ($token): string {
|
||||
$tokens = explode('_', $token);
|
||||
|
||||
return implode('', array_map('ucfirst', $tokens));
|
||||
|
@ -53,7 +53,7 @@ final class SimplifyEmptyArrayCheckRector extends AbstractRector
|
||||
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$node,
|
||||
// is_array(...)
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return $node instanceof FuncCall && $this->isName($node, 'is_array');
|
||||
},
|
||||
Empty_::class
|
||||
|
@ -181,7 +181,7 @@ CODE_SAMPLE
|
||||
return $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$binaryOp,
|
||||
Variable::class,
|
||||
function (Node $node, Node $otherNode) use ($expr) {
|
||||
function (Node $node, Node $otherNode) use ($expr): bool {
|
||||
return $this->areNodesEqual($otherNode, $expr);
|
||||
}
|
||||
);
|
||||
|
@ -56,10 +56,10 @@ final class GetClassToInstanceOfRector extends AbstractRector
|
||||
{
|
||||
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$node,
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return $this->isClassReference($node);
|
||||
},
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return $this->isGetClassFuncCallNode($node);
|
||||
}
|
||||
);
|
||||
|
@ -58,10 +58,10 @@ final class SimplifyArraySearchRector extends AbstractRector
|
||||
{
|
||||
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$node,
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return $node instanceof FuncCall && $this->isName($node, 'array_search');
|
||||
},
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return $this->isBool($node);
|
||||
}
|
||||
);
|
||||
|
@ -77,10 +77,10 @@ final class SimplifyConditionsRector extends AbstractRector
|
||||
{
|
||||
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$binaryOp,
|
||||
function (Node $binaryOp) {
|
||||
function (Node $binaryOp): bool {
|
||||
return $binaryOp instanceof Identical || $binaryOp instanceof NotIdentical;
|
||||
},
|
||||
function (Node $binaryOp) {
|
||||
function (Node $binaryOp): bool {
|
||||
return $this->isBool($binaryOp);
|
||||
}
|
||||
);
|
||||
|
@ -52,10 +52,10 @@ final class SimplifyTautologyTernaryRector extends AbstractRector
|
||||
|
||||
$isMatch = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$node->cond,
|
||||
function (Node $leftNode) use ($node) {
|
||||
function (Node $leftNode) use ($node): bool {
|
||||
return $this->areNodesEqual($leftNode, $node->if);
|
||||
},
|
||||
function (Node $leftNode) use ($node) {
|
||||
function (Node $leftNode) use ($node): bool {
|
||||
return $this->areNodesEqual($leftNode, $node->else);
|
||||
}
|
||||
);
|
||||
|
@ -50,10 +50,10 @@ final class IdenticalFalseToBooleanNotRector extends AbstractRector
|
||||
{
|
||||
$matchedNodes = $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
|
||||
$node,
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return ! $node instanceof BinaryOp;
|
||||
},
|
||||
function (Node $node) {
|
||||
function (Node $node): bool {
|
||||
return $this->isFalse($node);
|
||||
}
|
||||
);
|
||||
|
@ -220,7 +220,7 @@ CODE_SAMPLE
|
||||
$this->newUseStatements = [];
|
||||
$this->newFunctionUseStatements = [];
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node): ?Name {
|
||||
if (! $node instanceof Name) {
|
||||
return null;
|
||||
}
|
||||
@ -277,6 +277,8 @@ CODE_SAMPLE
|
||||
|
||||
return new Name($shortName);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// for doc blocks
|
||||
|
@ -34,7 +34,7 @@ final class DiffConsoleFormatter
|
||||
|
||||
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
|
||||
$string = Strings::replace($string, '#^(\+.*)#', '<fg=green>$1</fg=green>');
|
||||
// make "-" lines red
|
||||
|
@ -254,7 +254,7 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
|
||||
{
|
||||
foreach ($sections as $section) {
|
||||
$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 = trim($inlined);
|
||||
$inlined = '[' . $inlined . ']';
|
||||
|
@ -17,7 +17,7 @@ final class RectorsFinder
|
||||
{
|
||||
$allRectors = $this->findInDirectories([__DIR__ . '/../../../../packages', __DIR__ . '/../../../../src']);
|
||||
|
||||
return array_map(function (RectorInterface $rector) {
|
||||
return array_map(function (RectorInterface $rector): string {
|
||||
return get_class($rector);
|
||||
}, $allRectors);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ final class NodeInfoResult
|
||||
*/
|
||||
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();
|
||||
});
|
||||
|
||||
|
@ -99,7 +99,7 @@ CODE_SAMPLE
|
||||
*/
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@ -130,7 +130,7 @@ CODE_SAMPLE
|
||||
*/
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@ -216,7 +216,10 @@ CODE_SAMPLE
|
||||
// sort
|
||||
usort(
|
||||
$nodesByTypeAndPosition,
|
||||
function (VariableNodeUseInfo $firstVariableNodeUseInfo, VariableNodeUseInfo $secondVariableNodeUseInfo) {
|
||||
function (
|
||||
VariableNodeUseInfo $firstVariableNodeUseInfo,
|
||||
VariableNodeUseInfo $secondVariableNodeUseInfo
|
||||
): int {
|
||||
return $firstVariableNodeUseInfo->getStartTokenPosition() <=> $secondVariableNodeUseInfo->getStartTokenPosition();
|
||||
}
|
||||
);
|
||||
@ -285,7 +288,7 @@ CODE_SAMPLE
|
||||
|
||||
$isVariableAssigned = (bool) $this->betterNodeFinder->findFirst($assignNode->expr, function (Node $node) use (
|
||||
$nodeByTypeAndPosition
|
||||
) {
|
||||
): bool {
|
||||
return $this->areNodesEqual($node, $nodeByTypeAndPosition->getVariableNode());
|
||||
});
|
||||
|
||||
|
@ -209,7 +209,7 @@ CODE_SAMPLE
|
||||
private function resolveAssignRouteNodes(ClassMethod $classMethod): array
|
||||
{
|
||||
// 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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Traversable;
|
||||
|
||||
@ -91,12 +92,14 @@ abstract class AbstractTypeInfo
|
||||
*/
|
||||
public function getTypeNode(bool $forceFqn = false)
|
||||
{
|
||||
$types = $forceFqn ? $this->fqnTypes : $this->types;
|
||||
if (! $this->isTypehintAble()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $types[0];
|
||||
$type = $this->resolveTypeForTypehint($forceFqn);
|
||||
if ($type === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
if ($this->typeAnalyzer->isPhpReservedType($type)) {
|
||||
if ($this->isNullable) {
|
||||
@ -124,8 +127,12 @@ abstract class AbstractTypeInfo
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
@ -293,4 +300,50 @@ abstract class AbstractTypeInfo
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -410,7 +410,9 @@ final class DocBlockManipulator
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) {
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (
|
||||
AttributeAwareNodeInterface $node
|
||||
): AttributeAwareNodeInterface {
|
||||
if (! $node instanceof IdentifierTypeNode) {
|
||||
return $node;
|
||||
}
|
||||
@ -447,7 +449,7 @@ final class DocBlockManipulator
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) use (
|
||||
$namespacePrefix,
|
||||
$excludedClasses
|
||||
) {
|
||||
): AttributeAwareNodeInterface {
|
||||
if (! $node instanceof IdentifierTypeNode) {
|
||||
return $node;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ final class FqnNamePhpDocNodeDecorator implements PhpDocNodeDecoratorInterface
|
||||
{
|
||||
$this->nodeTraverser->traverseWithCallable(
|
||||
$attributeAwarePhpDocNode,
|
||||
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) use ($node) {
|
||||
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) use ($node): AttributeAwareNodeInterface {
|
||||
if (! $attributeAwarePhpDocNode instanceof IdentifierTypeNode) {
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
@ -77,7 +77,7 @@ final class FqnNamePhpDocNodeDecorator implements PhpDocNodeDecoratorInterface
|
||||
// collect to particular node types
|
||||
$this->nodeTraverser->traverseWithCallable(
|
||||
$attributeAwarePhpDocNode,
|
||||
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) {
|
||||
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode): AttributeAwareNodeInterface {
|
||||
if (! $this->isTypeAwareNode($attributeAwarePhpDocNode)) {
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ CODE_SAMPLE
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ CODE_SAMPLE
|
||||
/** @var FuncCall|null $keyFuncCall */
|
||||
$keyFuncCall = $this->betterNodeFinder->findFirst($nextExpression, function (Node $node) use (
|
||||
$resetOrEndFuncCall
|
||||
) {
|
||||
): bool {
|
||||
if (! $node instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ CODE_SAMPLE
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$nextExpression], function (Node $node) use (
|
||||
$resultVariable
|
||||
) {
|
||||
): ?Variable {
|
||||
if ($node instanceof FuncCall) {
|
||||
if ($this->isName($node, 'get_defined_vars')) {
|
||||
return $resultVariable;
|
||||
|
@ -129,7 +129,7 @@ CODE_SAMPLE
|
||||
|
||||
$stmt = $contentNodes[0]->expr;
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$stmt], function (Node $node) {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$stmt], function (Node $node): Node {
|
||||
if (! $node instanceof String_) {
|
||||
return $node;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ final class LetManipulator
|
||||
|
||||
$hasBeConstructedThrough = (bool) $this->betterNodeFinder->find(
|
||||
(array) $method->stmts,
|
||||
function (Node $node) {
|
||||
function (Node $node): ?bool {
|
||||
if (! $node instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ CODE_SAMPLE
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($node->stmts, function (Node $node) use (
|
||||
$classesUsingTypes
|
||||
) {
|
||||
): ?MethodCall {
|
||||
if (! $node instanceof New_) {
|
||||
return null;
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ CODE_SAMPLE
|
||||
foreach ($this->staticTypes as $implements => $staticType) {
|
||||
$containsEntityFactoryStaticCall = (bool) $this->betterNodeFinder->findFirst(
|
||||
$class->stmts,
|
||||
function (Node $node) use ($staticType) {
|
||||
function (Node $node) use ($staticType): bool {
|
||||
return $this->isEntityFactoryStaticCall($node, $staticType);
|
||||
}
|
||||
);
|
||||
|
@ -148,7 +148,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
// "$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');
|
||||
});
|
||||
|
||||
@ -158,7 +158,7 @@ CODE_SAMPLE
|
||||
|
||||
// "$this->get('request')"
|
||||
/** @var MethodCall[] $getMethodCalls */
|
||||
$getMethodCalls = $this->betterNodeFinder->find($node, function (Node $node) {
|
||||
$getMethodCalls = $this->betterNodeFinder->find($node, function (Node $node): bool {
|
||||
if (! $node instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->betterNodeFinder->findFirst([$nextExpression], function (Node $node) {
|
||||
return $this->betterNodeFinder->findFirst([$nextExpression], function (Node $node): bool {
|
||||
if (! $node instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
@ -155,7 +155,9 @@ CODE_SAMPLE
|
||||
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$node->expr], function (Node $node) {
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable([$node->expr], function (Node $node): ?Node {
|
||||
if (! $node instanceof ArrayItem) {
|
||||
return null;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -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']));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -155,3 +155,6 @@ parameters:
|
||||
- '#Empty array passed to foreach#'
|
||||
- '#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#'
|
||||
- '#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\>#'
|
||||
|
@ -15,4 +15,5 @@ parameters:
|
||||
php_version_features: '7.1'
|
||||
|
||||
services:
|
||||
Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
|
||||
# Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
|
||||
Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector: ~
|
||||
|
@ -9,8 +9,8 @@ use ReflectionFunction;
|
||||
final class CallableCollectorPopulator
|
||||
{
|
||||
/**
|
||||
* @param string[]|callable[]|Closure[] $callables
|
||||
* @return callable[]
|
||||
* @param string[]|Closure[]|mixed[] $callables
|
||||
* @return Closure[]
|
||||
*/
|
||||
public function populate(array $callables): array
|
||||
{
|
||||
|
@ -124,7 +124,7 @@ final class FilesFinder
|
||||
return;
|
||||
}
|
||||
|
||||
$finder->filter(function (SplFileInfo $splFileInfo) {
|
||||
$finder->filter(function (SplFileInfo $splFileInfo): bool {
|
||||
// return false to remove file
|
||||
foreach ($this->excludePaths as $excludePath) {
|
||||
if (Strings::match($splFileInfo->getRealPath(), '#' . preg_quote($excludePath, '#') . '#')) {
|
||||
|
@ -185,7 +185,7 @@ final class RegexPatternArgumentManipulator
|
||||
}
|
||||
|
||||
/** @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) {
|
||||
return null;
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ final class BetterNodeFinder
|
||||
{
|
||||
$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)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ final class BinaryOpManipulator
|
||||
return $condition;
|
||||
}
|
||||
|
||||
return function (Node $node) use ($condition) {
|
||||
return function (Node $node) use ($condition): bool {
|
||||
return is_a($node, $condition, true);
|
||||
};
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ final class CallManipulator
|
||||
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@ -134,7 +134,7 @@ final class CallManipulator
|
||||
$externalFunctionNode = $this->betterNodeFinder->findFirst($externalFileContent, function (Node $node) use (
|
||||
$requiredExternalType,
|
||||
$functionName
|
||||
) {
|
||||
): ?bool {
|
||||
if (! is_a($node, $requiredExternalType, true)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ final class ClassConstManipulator
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->betterNodeFinder->find($classNode, function (Node $node) use ($classConst) {
|
||||
return $this->betterNodeFinder->find($classNode, function (Node $node) use ($classConst): bool {
|
||||
// itself
|
||||
if ($this->betterStandardPrinter->areNodesEqual($node, $classConst)) {
|
||||
return false;
|
||||
|
@ -236,7 +236,7 @@ final class ClassManipulator
|
||||
*/
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ final class ClassMethodManipulator
|
||||
{
|
||||
$isUsedDirectly = (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use (
|
||||
$param
|
||||
) {
|
||||
): bool {
|
||||
return $this->betterStandardPrinter->areNodesEqual($node, $param->var);
|
||||
});
|
||||
|
||||
@ -77,7 +77,7 @@ final class ClassMethodManipulator
|
||||
}
|
||||
|
||||
/** @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) {
|
||||
return false;
|
||||
}
|
||||
@ -152,7 +152,7 @@ final class ClassMethodManipulator
|
||||
*/
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Rector\PhpParser\Node\Manipulator;
|
||||
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
@ -42,7 +43,7 @@ final class FunctionLikeManipulator
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ final class ValueResolver
|
||||
return $this->constExprEvaluator;
|
||||
}
|
||||
|
||||
$this->constExprEvaluator = new ConstExprEvaluator(function (Expr $expr) {
|
||||
$this->constExprEvaluator = new ConstExprEvaluator(function (Expr $expr): ?string {
|
||||
if ($expr instanceof Dir) {
|
||||
// __DIR__
|
||||
return $this->resolveDirConstant($expr);
|
||||
|
@ -77,7 +77,7 @@ trait BetterStandardPrinterTrait
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ final class RenameClassRector extends AbstractRector
|
||||
* @var string[]
|
||||
*/
|
||||
private $alreadyProcessedClasses = [];
|
||||
|
||||
/**
|
||||
* @var ClassNaming
|
||||
*/
|
||||
@ -50,8 +51,7 @@ final class RenameClassRector extends AbstractRector
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
ClassNaming $classNaming,
|
||||
array $oldToNewClasses = []
|
||||
)
|
||||
{
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->classNaming = $classNaming;
|
||||
$this->oldToNewClasses = $oldToNewClasses;
|
||||
@ -120,21 +120,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
if ($node instanceof Name) {
|
||||
$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);
|
||||
return $this->refactorName($node);
|
||||
}
|
||||
|
||||
if ($node instanceof Namespace_) {
|
||||
@ -145,7 +131,7 @@ CODE_SAMPLE
|
||||
$node = $this->refactorClassLikeNode($node);
|
||||
}
|
||||
|
||||
if (! $node) {
|
||||
if ($node === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -222,7 +208,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
$classNode = $this->getClassOfNamespaceToRefactor($node);
|
||||
if (! $classNode) {
|
||||
if ($classNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -243,7 +229,7 @@ CODE_SAMPLE
|
||||
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@ -286,4 +272,23 @@ CODE_SAMPLE
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace Rector\Rector\Psr4;
|
||||
|
||||
use Nette\Utils\FileSystem;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\FileSystemRector\Rector\AbstractFileSystemRector;
|
||||
@ -123,7 +124,7 @@ CODE_SAMPLE
|
||||
/** @var Class_[] $classNodes */
|
||||
$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;
|
||||
});
|
||||
|
||||
|
@ -41,7 +41,7 @@ final class FunctionReflectionResolver
|
||||
$nodes = $this->parser->parseFile($stubFileLocation);
|
||||
|
||||
/** @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_) {
|
||||
return false;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user