[TypeDeclaration] Add ReturnTypeFromStrictTypedCallRector (#5573)

This commit is contained in:
Tomas Votruba 2021-02-16 17:12:37 +01:00 committed by GitHub
parent a3f3b0e7d2
commit 651573a58f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1267 additions and 20 deletions

View File

@ -190,6 +190,7 @@
"rules/symfony4/tests/Rector/MethodCall/ContainerGetToConstructorInjectionRector/Source"
],
"files": [
"rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Source/external_bool_function.php",
"rules/restoration/tests/Rector/Use_/RestoreFullyQualifiedNameRector/Source/ShortClassOnly.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/AnotherClass.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/Foo.php",

View File

@ -2,12 +2,20 @@
declare(strict_types=1);
return static function (
\Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator
): void {
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeFromStrictTypedPropertyRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector;
use Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(\Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector::class);
$services->set(\Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector::class);
$services->set(\Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector::class);
$services->set(\Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeFromStrictTypedPropertyRector::class);
$services->set(AddClosureReturnTypeRector::class);
$services->set(ReturnTypeFromStrictTypedPropertyRector::class);
$services->set(TypedPropertyFromStrictConstructorRector::class);
$services->set(ParamTypeFromStrictTypedPropertyRector::class);
$services->set(ReturnTypeFromStrictTypedCallRector::class);
$services->set(AddVoidReturnTypeWhereNoReturnRector::class);
};

View File

@ -2,14 +2,20 @@
declare(strict_types=1);
return static function (
\Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator
): void {
use Rector\TypeDeclaration\Rector\ClassMethod\AddArrayParamDocTypeRector;
use Rector\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector;
use Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector;
use Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector;
use Rector\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector;
use Rector\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(\Rector\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector::class);
$services->set(\Rector\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector::class);
$services->set(\Rector\TypeDeclaration\Rector\Property\PropertyTypeDeclarationRector::class);
$services->set(\Rector\TypeDeclaration\Rector\Closure\AddClosureReturnTypeRector::class);
$services->set(\Rector\TypeDeclaration\Rector\ClassMethod\AddArrayParamDocTypeRector::class);
$services->set(\Rector\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector::class);
$services->set(ParamTypeDeclarationRector::class);
$services->set(ReturnTypeDeclarationRector::class);
$services->set(PropertyTypeDeclarationRector::class);
$services->set(AddClosureReturnTypeRector::class);
$services->set(AddArrayParamDocTypeRector::class);
$services->set(AddArrayReturnDocTypeRector::class);
};

View File

@ -188,6 +188,16 @@ final class NodeRepository
return $this->functionsByName[$name] ?? null;
}
public function findFunctionByFuncCall(FuncCall $funcCall): ?Function_
{
$functionName = $this->nodeNameResolver->getName($funcCall);
if ($functionName === null) {
return null;
}
return $this->findFunction($functionName);
}
/**
* @return MethodCall[][]|StaticCall[][]
*/

View File

@ -544,3 +544,9 @@ parameters:
- '#Property PhpParser\\Node\\Param\:\:\$type \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#'
- '#Binary operation "\." between array\|string\|false and (.*?) results in an error#'
- '#Parameter \#3 \.\.\.\$rest of function array_uintersect expects array, Closure\(PhpParser\\Node\\Param, PhpParser\\Node\\Param\)\: int given#'
- '#Method Rector\\TypeDeclaration\\Rector\\ClassMethod\\ReturnTypeFromStrictTypedCallRector\:\:collectStrictReturnTypes\(\) should return array<PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\> but returns array<int, PhpParser\\Node\>#'
# stmts vs getStmts() issue
- '#Access to an undefined property PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Stmt\\ClassMethod\|PhpParser\\Node\\Stmt\\Function_\:\:\$stmts#'
- '#Parameter \#1 \$functionLike of method Rector\\TypeDeclaration\\TypeInferer\\SilentVoidResolver\:\:hasExlusiveVoid\(\) expects PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Stmt\\ClassMethod\|PhpParser\\Node\\Stmt\\Function_, PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Stmt\\ClassMethod\|PhpParser\\Node\\Stmt\\Function_ given#'

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\NodeAnalyzer;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
final class TypeNodeUnwrapper
{
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
public function __construct(BetterStandardPrinter $betterStandardPrinter)
{
$this->betterStandardPrinter = $betterStandardPrinter;
}
/**
* @param array<UnionType|NullableType|Name|Identifier> $typeNodes
* @return array<Name|Identifier>
*/
public function unwrapNullableUnionTypes(array $typeNodes): array
{
$unwrappedTypeNodes = [];
foreach ($typeNodes as $typeNode) {
if ($typeNode instanceof UnionType) {
$unwrappedTypeNodes = array_merge($unwrappedTypeNodes, $typeNode->types);
} elseif ($typeNode instanceof NullableType) {
$unwrappedTypeNodes[] = $typeNode->type;
$unwrappedTypeNodes[] = new Identifier('null');
} else {
$unwrappedTypeNodes[] = $typeNode;
}
}
return $this->uniquateNodes($unwrappedTypeNodes);
}
/**
* @param Node[] $nodes
* @return Node[]
*/
public function uniquateNodes(array $nodes): array
{
$uniqueNodes = [];
foreach ($nodes as $node) {
$uniqueHash = $this->betterStandardPrinter->printWithoutComments($node);
$uniqueNodes[$uniqueHash] = $node;
}
// reset keys from 0, for further compatibility
return array_values($uniqueNodes);
}
}

View File

@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\CodingStyle\ValueObject\ObjectMagicMethods;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\AddVoidReturnTypeWhereNoReturnRectorTest
*/
final class AddVoidReturnTypeWhereNoReturnRector extends AbstractRector
{
/**
* @var SilentVoidResolver
*/
private $silentVoidResolver;
public function __construct(SilentVoidResolver $silentVoidResolver)
{
$this->silentVoidResolver = $silentVoidResolver;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add return type void to function like without any return', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
public function getValues()
{
$value = 1000;
return;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
public function getValues(): void
{
$value = 1000;
return;
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class];
}
/**
* @param ClassMethod|Function_|Closure|ArrowFunction $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
return null;
}
if ($node->returnType !== null) {
return null;
}
if ($this->isNames($node, ObjectMagicMethods::METHOD_NAMES)) {
return null;
}
if (! $this->silentVoidResolver->hasExlusiveVoid($node)) {
return null;
}
if ($this->isAtLeastPhpVersion(PhpVersionFeature::VOID_TYPE)) {
$node->returnType = new Identifier('void');
}
return $node;
}
}

View File

@ -0,0 +1,218 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\UnionType;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\Type\Type;
use Rector\CodingStyle\ValueObject\ObjectMagicMethods;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\NodeAnalyzer\TypeNodeUnwrapper;
use Rector\TypeDeclaration\Reflection\ReflectionTypeResolver;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\ReturnTypeFromStrictTypedCallRectorTest
*/
final class ReturnTypeFromStrictTypedCallRector extends AbstractRector
{
/**
* @var ReflectionTypeResolver
*/
private $reflectionTypeResolver;
/**
* @var TypeNodeUnwrapper
*/
private $typeNodeUnwrapper;
public function __construct(ReflectionTypeResolver $reflectionTypeResolver, TypeNodeUnwrapper $typeNodeUnwrapper)
{
$this->reflectionTypeResolver = $reflectionTypeResolver;
$this->typeNodeUnwrapper = $typeNodeUnwrapper;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add return type from strict return type of call', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
public function getData()
{
return $this->getNumber();
}
private function getNumber(): int
{
return 1000;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
public function getData(): int
{
return $this->getNumber();
}
private function getNumber(): int
{
return 1000;
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class];
}
/**
* @param ClassMethod|Function_|Closure|ArrowFunction $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
return null;
}
if ($node->returnType !== null) {
return null;
}
if ($this->isNames($node, ObjectMagicMethods::METHOD_NAMES)) {
return null;
}
/** @var Return_[] $returns */
$returns = $this->betterNodeFinder->findInstanceOf((array) $node->stmts, Return_::class);
$returnedStrictTypes = $this->collectStrictReturnTypes($returns);
if ($returnedStrictTypes === []) {
return null;
}
if (count($returnedStrictTypes) === 1) {
$node->returnType = $returnedStrictTypes[0];
return $node;
}
if ($this->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
$unwrappedTypes = $this->typeNodeUnwrapper->unwrapNullableUnionTypes($returnedStrictTypes);
$node->returnType = new UnionType($unwrappedTypes);
return $node;
}
return null;
}
/**
* @param Return_[] $returns
* @return array<Name|NullableType|UnionType>
*/
private function collectStrictReturnTypes(array $returns): array
{
$returnedStrictTypeNodes = [];
foreach ($returns as $return) {
if ($return->expr === null) {
return [];
}
$returnedExpr = $return->expr;
if ($returnedExpr instanceof MethodCall) {
$returnNode = $this->resolveMethodCallReturnNode($returnedExpr);
} elseif ($returnedExpr instanceof StaticCall) {
$returnNode = $this->resolveStaticCallReturnNode($returnedExpr);
} elseif ($returnedExpr instanceof FuncCall) {
$returnNode = $this->resolveFuncCallReturnNode($returnedExpr);
} else {
return [];
}
if (! $returnNode instanceof Node) {
return [];
}
$returnedStrictTypeNodes[] = $returnNode;
}
return $this->typeNodeUnwrapper->uniquateNodes($returnedStrictTypeNodes);
}
private function resolveMethodCallReturnNode(MethodCall $methodCall): ?Node
{
$classMethod = $this->nodeRepository->findClassMethodByMethodCall($methodCall);
if ($classMethod instanceof ClassMethod) {
return $classMethod->returnType;
}
$returnType = $this->reflectionTypeResolver->resolveMethodCallReturnType($methodCall);
if (! $returnType instanceof Type) {
return null;
}
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
}
private function resolveStaticCallReturnNode(StaticCall $staticCall): ?Node
{
$classMethod = $this->nodeRepository->findClassMethodByStaticCall($staticCall);
if ($classMethod instanceof ClassMethod) {
return $classMethod->returnType;
}
$returnType = $this->reflectionTypeResolver->resolveStaticCallReturnType($staticCall);
if (! $returnType instanceof Type) {
return null;
}
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
}
/**
* @return Identifier|Name|NullableType|PhpParserUnionType|null
*/
private function resolveFuncCallReturnNode(FuncCall $funcCall): ?Node
{
$function = $this->nodeRepository->findFunctionByFuncCall($funcCall);
if ($function instanceof Function_) {
return $function->returnType;
}
$returnType = $this->reflectionTypeResolver->resolveFuncCallReturnType($funcCall);
if (! $returnType instanceof Type) {
return null;
}
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
}
}

View File

@ -4,7 +4,13 @@ declare(strict_types=1);
namespace Rector\TypeDeclaration\Reflection;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Reflection\Php\PhpFunctionReflection;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Type;
@ -12,6 +18,7 @@ use PHPStan\Type\TypeWithClassName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
final class ReflectionTypeResolver
{
@ -30,14 +37,51 @@ final class ReflectionTypeResolver
*/
private $nodeNameResolver;
/**
* @var PrivatesCaller
*/
private $privatesCaller;
public function __construct(
NodeTypeResolver $nodeTypeResolver,
ReflectionProvider $reflectionProvider,
NodeNameResolver $nodeNameResolver
NodeNameResolver $nodeNameResolver,
PrivatesCaller $privatesCaller
) {
$this->nodeTypeResolver = $nodeTypeResolver;
$this->reflectionProvider = $reflectionProvider;
$this->nodeNameResolver = $nodeNameResolver;
$this->privatesCaller = $privatesCaller;
}
public function resolveMethodCallReturnType(MethodCall $methodCall): ?Type
{
$objectType = $this->nodeTypeResolver->resolve($methodCall->var);
if (! $objectType instanceof TypeWithClassName) {
return null;
}
$methodName = $this->nodeNameResolver->getName($methodCall->name);
if ($methodName === null) {
return null;
}
return $this->resolveNativeReturnTypeFromClassAndMethod($objectType->getClassName(), $methodName, $methodCall);
}
public function resolveStaticCallReturnType(StaticCall $staticCall): ?Type
{
$className = $this->nodeNameResolver->getName($staticCall->class);
if ($className === null) {
return null;
}
$methodName = $this->nodeNameResolver->getName($staticCall->name);
if ($methodName === null) {
return null;
}
return $this->resolveNativeReturnTypeFromClassAndMethod($className, $methodName, $staticCall);
}
public function resolvePropertyFetchType(PropertyFetch $propertyFetch): ?Type
@ -64,4 +108,46 @@ final class ReflectionTypeResolver
return null;
}
public function resolveFuncCallReturnType(FuncCall $funcCall): ?Type
{
$funcCallScope = $funcCall->getAttribute(AttributeKey::SCOPE);
$funcCallName = $funcCall->name;
if ($funcCallName instanceof Expr) {
return null;
}
if (! $this->reflectionProvider->hasFunction($funcCallName, $funcCallScope)) {
return null;
}
$functionReflection = $this->reflectionProvider->getFunction($funcCallName, $funcCallScope);
if (! $functionReflection instanceof PhpFunctionReflection) {
return null;
}
return $this->privatesCaller->callPrivateMethod($functionReflection, 'getNativeReturnType', []);
}
private function resolveNativeReturnTypeFromClassAndMethod(string $className, string $methodName, Expr $expr): ?Type
{
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}
$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->hasMethod($methodName)) {
return null;
}
$callerScope = $expr->getAttribute(AttributeKey::SCOPE);
$methodReflection = $classReflection->getMethod($methodName, $callerScope);
if (! $methodReflection instanceof PhpMethodReflection) {
return null;
}
return $this->privatesCaller->callPrivateMethod($methodReflection, 'getNativeReturnType', []);
}
}

View File

@ -5,18 +5,57 @@ declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Switch_;
use PhpParser\Node\Stmt\Throw_;
use PhpParser\Node\Stmt\TryCatch;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class SilentVoidResolver
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(BetterNodeFinder $betterNodeFinder)
{
$this->betterNodeFinder = $betterNodeFinder;
}
/**
* @param ClassMethod|Closure|Function_ $functionLike
*/
public function hasExlusiveVoid(FunctionLike $functionLike): bool
{
$classLike = $functionLike->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike instanceof Interface_) {
return false;
}
if ($this->betterNodeFinder->hasInstancesOf((array) $functionLike->stmts, [Yield_::class])) {
return false;
}
/** @var Return_[] $returns */
$returns = $this->betterNodeFinder->findInstanceOf((array) $functionLike->stmts, Return_::class);
foreach ($returns as $return) {
if ($return->expr !== null) {
return false;
}
}
return true;
}
/**
* @param ClassMethod|Closure|Function_ $functionLike
*/
@ -69,10 +108,12 @@ final class SilentVoidResolver
$casesWithReturn = 0;
foreach ($switch->cases as $case) {
foreach ($case->stmts as $caseStmt) {
if ($caseStmt instanceof Return_) {
++$casesWithReturn;
break;
if (! $caseStmt instanceof Return_) {
continue;
}
++$casesWithReturn;
break;
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class AddVoidReturnTypeWhereNoReturnRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return AddVoidReturnTypeWhereNoReturnRector::class;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\Fixture;
use Iterator;
final class SkipExistingType
{
public function getValues(): Iterator
{
yield [100];
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\Fixture;
final class SkipReturnOfSomething
{
public function getValues()
{
$value = 1000;
if ($value) {
return;
}
return 10;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\Fixture;
final class SkipYield
{
public function getValues()
{
yield [1000];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\Fixture;
final class SomeClass
{
public function getValues()
{
$value = 1000;
return;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\Fixture;
final class SomeClass
{
public function getValues(): void
{
$value = 1000;
return;
}
}
?>

View File

@ -0,0 +1,43 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class AlsoStaticCall
{
public function getData()
{
return ValuableData::getNumber();
}
}
final class ValuableData
{
public static function getNumber(): int
{
return 1000;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class AlsoStaticCall
{
public function getData(): int
{
return ValuableData::getNumber();
}
}
final class ValuableData
{
public static function getNumber(): int
{
return 1000;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source\SomeExternalCaller;
final class ExternalCaller
{
public function getData(SomeExternalCaller $someExternalCaller)
{
return $someExternalCaller->getName();
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source\SomeExternalCaller;
final class ExternalCaller
{
public function getData(SomeExternalCaller $someExternalCaller): string
{
return $someExternalCaller->getName();
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
use function Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source\getExternalBool;
final class ExternalFunctionCaller
{
public function getData()
{
return getExternalBool();
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
use function Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source\getExternalBool;
final class ExternalFunctionCaller
{
public function getData(): bool
{
return getExternalBool();
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source\SomeExternalStaticCaller;
final class ExternalStaticCaller
{
public function getData()
{
return SomeExternalStaticCaller::getNumbers();
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
use Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source\SomeExternalStaticCaller;
final class ExternalStaticCaller
{
public function getData(): int
{
return SomeExternalStaticCaller::getNumbers();
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class ReturnFunction
{
public function getData()
{
return getMeBool();
}
}
function getMeBool(): bool
{
return true;
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class ReturnFunction
{
public function getData(): bool
{
return getMeBool();
}
}
function getMeBool(): bool
{
return true;
}
?>

View File

@ -0,0 +1,11 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class SkipConstructorForVoid
{
public function __construct()
{
$value = 1000;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
interface SkipInterfaceForVoid
{
public function someMethod();
}

View File

@ -0,0 +1,29 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class SkipOnceTypeAndOnceNot
{
public function getData()
{
if (mt_rand(0, 100)) {
return $this->getNumber();
}
return $this->getRandom();
}
private function getNumber(): int
{
return 100;
}
private function getRandom()
{
if (mt_rand(0, 1)) {
return true;
}
return '...';
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class SkipTwiceDifferentType
{
public function getData()
{
if (mt_rand(0, 100)) {
return $this->getNumber();
}
return $this->getString();
}
private function getNumber(): int
{
return 100;
}
private function getString(): string
{
return 'hey';
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class SomeClass
{
public function getData()
{
return $this->getNumber();
}
private function getNumber(): int
{
return 1000;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class SomeClass
{
public function getData(): int
{
return $this->getNumber();
}
private function getNumber(): int
{
return 1000;
}
}
?>

View File

@ -0,0 +1,45 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class TwiceSameType
{
public function getData()
{
if (mt_rand(0, 100)) {
return $this->getNumber(100);
}
return $this->getNumber(10);
}
private function getNumber(int $value): int
{
return $value;
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Fixture;
final class TwiceSameType
{
public function getData(): int
{
if (mt_rand(0, 100)) {
return $this->getNumber(100);
}
return $this->getNumber(10);
}
private function getNumber(int $value): int
{
return $value;
}
}
?>

View File

@ -0,0 +1,55 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\FixturePhp80;
final class MixOfNullableTypes
{
public function getData()
{
if (mt_rand(0, 100)) {
return $this->getFirstMix();
}
return $this->getSecondMixed();
}
private function getFirstMix(): ?int
{
return 100;
}
private function getSecondMixed(): ?string
{
return 'hey';
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\FixturePhp80;
final class MixOfNullableTypes
{
public function getData(): int|null|string
{
if (mt_rand(0, 100)) {
return $this->getFirstMix();
}
return $this->getSecondMixed();
}
private function getFirstMix(): ?int
{
return 100;
}
private function getSecondMixed(): ?string
{
return 'hey';
}
}
?>

View File

@ -0,0 +1,55 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\FixturePhp80;
final class MixOfUnionTypes
{
public function getData()
{
if (mt_rand(0, 100)) {
return $this->getFirstMix();
}
return $this->getSecondMixed();
}
private function getFirstMix(): int|string
{
return 100;
}
private function getSecondMixed(): string|bool
{
return 'hey';
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\FixturePhp80;
final class MixOfUnionTypes
{
public function getData(): int|string|bool
{
if (mt_rand(0, 100)) {
return $this->getFirstMix();
}
return $this->getSecondMixed();
}
private function getFirstMix(): int|string
{
return 100;
}
private function getSecondMixed(): string|bool
{
return 'hey';
}
}
?>

View File

@ -0,0 +1,55 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\FixturePhp80;
final class TwiceDifferentType
{
public function getData()
{
if (mt_rand(0, 100)) {
return $this->getNumber();
}
return $this->getString();
}
private function getNumber(): int
{
return 100;
}
private function getString(): string
{
return 'hey';
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\FixturePhp80;
final class TwiceDifferentType
{
public function getData(): int|string
{
if (mt_rand(0, 100)) {
return $this->getNumber();
}
return $this->getString();
}
private function getNumber(): int
{
return 100;
}
private function getString(): string
{
return 'hey';
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class Php80Test extends AbstractRectorTestCase
{
/**
* @requires PHP 8.0
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp80');
}
protected function getRectorClass(): string
{
return ReturnTypeFromStrictTypedCallRector::class;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ReturnTypeFromStrictTypedCallRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source;
final class SomeExternalCaller
{
public function getName(): string
{
return 'Yesman';
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source;
final class SomeExternalStaticCaller
{
public static function getNumbers(): int
{
return 1000;
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector\Source;
function getExternalBool(): bool
{
return true;
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::UNION_TYPES - 1);
$services = $containerConfigurator->services();
$services->set(ReturnTypeFromStrictTypedCallRector::class);
};