[TypeDeclarationg] Add Tokens iterator to AddArrayReturnDocTypeRector and AddArrayParamDocTypeRector (#5796)

Co-authored-by: kaizen-ci <info@kaizen-ci.org>
This commit is contained in:
Tomas Votruba 2021-03-08 15:52:36 +01:00 committed by GitHub
parent 5f70de5502
commit 80c4fc8aab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1407 additions and 258 deletions

View File

@ -49,6 +49,9 @@ final class MultilineTest extends AbstractPhpDocInfoPrinterTest
yield [__DIR__ . '/Source/Multiline/multiline5.txt', new Nop()];
}
/**
* @return Iterator<string[]|Class_[]>
*/
public function provideDataClass(): Iterator
{
yield [__DIR__ . '/Source/Class_/some_entity_class.txt', new Class_(SomeEntityClass::class)];

View File

@ -37,6 +37,9 @@ final class TagValueNodeReprintTest extends AbstractPhpDocInfoTest
$this->doTestPrintedPhpDocInfo($fileInfo, $tagValueNodeClass);
}
/**
* @return Iterator<mixed[]>
*/
public function provideData(): Iterator
{
foreach ($this->getDirectoriesByTagValueNodes() as $tagValueNode => $directory) {
@ -50,7 +53,7 @@ final class TagValueNodeReprintTest extends AbstractPhpDocInfoTest
}
/**
* @return string[]
* @return array<class-string, string>
*/
private function getDirectoriesByTagValueNodes(): array
{

View File

@ -60,6 +60,9 @@ final class TypeNodeAnalyzerTest extends AbstractKernelTestCase
$this->assertSame($expectedIs, $isIntersection);
}
/**
* @return Iterator<IntersectionTypeNode[]|bool[]>
*/
public function provideDataForIntersectionAndNotNullable(): Iterator
{
yield [new IntersectionTypeNode([new IdentifierTypeNode(self::INT)]), true];

View File

@ -61,6 +61,9 @@ final class TagValueNodeConfigurationFactoryTest extends AbstractKernelTestCase
$this->assertSame(':', $tagValueNodeConfiguration->getArrayEqualSign());
}
/**
* @return Iterator<string[]>
*/
public function provideData(): Iterator
{
yield ['(type="integer", nullable=true, options={"default":0})'];

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeTypeCorrector;
use PHPStan\Type\Accessory\HasOffsetType;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
final class HasOffsetTypeCorrector
{
/**
* HasOffsetType breaks array mixed type, so we better get rid of it
*/
public function correct(Type $type): Type
{
if (! $type instanceof IntersectionType) {
return $type;
}
$clearTypes = [];
foreach ($type->getTypes() as $intersectionedType) {
if ($intersectionedType instanceof HasOffsetType) {
continue;
}
if ($intersectionedType instanceof NonEmptyArrayType) {
continue;
}
$clearTypes[] = $intersectionedType;
}
if (count($clearTypes) === 1) {
return $clearTypes[0];
}
return new IntersectionType($clearTypes);
}
}

View File

@ -10,10 +10,12 @@ use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
@ -22,6 +24,7 @@ use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
@ -38,6 +41,8 @@ use Rector\Core\NodeAnalyzer\ClassAnalyzer;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector;
use Rector\NodeTypeResolver\NodeTypeCorrector\HasOffsetTypeCorrector;
use Rector\NodeTypeResolver\NodeTypeResolver\IdentifierTypeResolver;
use Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer;
use Rector\StaticTypeMapper\TypeFactory\UnionTypeFactory;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
@ -81,6 +86,16 @@ final class NodeTypeResolver
*/
private $reflectionProvider;
/**
* @var HasOffsetTypeCorrector
*/
private $hasOffsetTypeCorrector;
/**
* @var IdentifierTypeResolver
*/
private $identifierTypeResolver;
/**
* @param NodeTypeResolverInterface[] $nodeTypeResolvers
*/
@ -90,6 +105,8 @@ final class NodeTypeResolver
GenericClassStringTypeCorrector $genericClassStringTypeCorrector,
UnionTypeFactory $unionTypeFactory,
ReflectionProvider $reflectionProvider,
HasOffsetTypeCorrector $hasOffsetTypeCorrector,
IdentifierTypeResolver $identifierTypeResolver,
array $nodeTypeResolvers
) {
foreach ($nodeTypeResolvers as $nodeTypeResolver) {
@ -101,6 +118,8 @@ final class NodeTypeResolver
$this->genericClassStringTypeCorrector = $genericClassStringTypeCorrector;
$this->unionTypeFactory = $unionTypeFactory;
$this->reflectionProvider = $reflectionProvider;
$this->hasOffsetTypeCorrector = $hasOffsetTypeCorrector;
$this->identifierTypeResolver = $identifierTypeResolver;
}
/**
@ -154,7 +173,7 @@ final class NodeTypeResolver
{
$type = $this->resolveByNodeTypeResolvers($node);
if ($type !== null) {
return $type;
return $this->hasOffsetTypeCorrector->correct($type);
}
$scope = $node->getAttribute(AttributeKey::SCOPE);
@ -163,6 +182,11 @@ final class NodeTypeResolver
}
if (! $node instanceof Expr) {
// scalar type, e.g. from param type name
if ($node instanceof Identifier) {
return $this->identifierTypeResolver->resolve($node);
}
return new MixedType();
}
@ -214,6 +238,10 @@ final class NodeTypeResolver
return $this->resolve($node);
}
if ($node instanceof Return_) {
return $this->resolve($node);
}
if (! $node instanceof Expr) {
return new MixedType();
}
@ -239,11 +267,15 @@ final class NodeTypeResolver
}
$staticType = $scope->getType($node);
if (! $staticType instanceof ObjectType) {
if ($staticType instanceof GenericObjectType) {
return $staticType;
}
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAliasedObjectType($node, $staticType);
if ($staticType instanceof ObjectType) {
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAliasedObjectType($node, $staticType);
}
return $staticType;
}
public function isNumberType(Node $node): bool

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node\Identifier;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
final class IdentifierTypeResolver
{
public function resolve(Identifier $identifier): Type
{
if ($identifier->toLowerString() === 'string') {
return new StringType();
}
if ($identifier->toLowerString() === 'bool') {
return new BooleanType();
}
if ($identifier->toLowerString() === 'int') {
return new IntegerType();
}
if ($identifier->toLowerString() === 'float') {
return new FloatType();
}
return new MixedType();
}
}

View File

@ -5,18 +5,19 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class StaticCallTypeResolver implements NodeTypeResolverInterface
final class StaticCallMethodCallTypeResolver implements NodeTypeResolverInterface
{
/**
* @var NodeTypeResolver
@ -42,7 +43,7 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface
/**
* @required
*/
public function autowireStaticCallTypeResolver(NodeTypeResolver $nodeTypeResolver): void
public function autowireStaticCallMethodCallTypeResolver(NodeTypeResolver $nodeTypeResolver): void
{
$this->nodeTypeResolver = $nodeTypeResolver;
}
@ -52,37 +53,55 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface
*/
public function getNodeClasses(): array
{
return [StaticCall::class];
return [StaticCall::class, MethodCall::class];
}
/**
* @param StaticCall $node
* @param StaticCall|MethodCall $node
*/
public function resolve(Node $node): Type
{
$classType = $this->nodeTypeResolver->resolve($node->class);
if ($node instanceof MethodCall) {
$callerType = $this->nodeTypeResolver->resolve($node->var);
} else {
$callerType = $this->nodeTypeResolver->resolve($node->class);
}
$methodName = $this->nodeNameResolver->getName($node->name);
// no specific method found, return class types, e.g. <ClassType>::$method()
if (! is_string($methodName)) {
return $classType;
return new MixedType();
}
if (! $classType instanceof ObjectType) {
return $classType;
}
if (! $this->reflectionProvider->hasClass($classType->getClassName())) {
return $classType;
}
$classReflection = $this->reflectionProvider->getClass($classType->getClassName());
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return $classType;
return new MixedType();
}
$nodeReturnType = $scope->getType($node);
if (! $nodeReturnType instanceof MixedType) {
return $nodeReturnType;
}
foreach ($callerType->getReferencedClasses() as $referencedClass) {
$classMethodReturnType = $this->resolveClassMethodReturnType($referencedClass, $methodName, $scope);
if (! $classMethodReturnType instanceof MixedType) {
return $classMethodReturnType;
}
}
return new MixedType();
}
private function resolveClassMethodReturnType(string $referencedClass, string $methodName, Scope $scope): Type
{
if (! $this->reflectionProvider->hasClass($referencedClass)) {
return new MixedType();
}
$classReflection = $this->reflectionProvider->getClass($referencedClass);
foreach ($classReflection->getAncestors() as $ancestorClassReflection) {
if (! $ancestorClassReflection->hasMethod($methodName)) {
continue;
@ -95,6 +114,6 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface
}
}
return $classType;
return new MixedType();
}
}

View File

@ -30,7 +30,7 @@ final class StaticTypeAnalyzer
}
if ($type instanceof ArrayType) {
return false;
return $this->isAlwaysTruableArrayType($type);
}
if ($this->isNullable($type)) {
@ -91,4 +91,10 @@ final class StaticTypeAnalyzer
return true;
}
private function isAlwaysTruableArrayType(ArrayType $arrayType): bool
{
$itemType = $arrayType->getItemType();
return $itemType instanceof ConstantScalarType && $itemType->getValue();
}
}

View File

@ -4,7 +4,9 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan\Type;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
@ -14,7 +16,7 @@ use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\VerbosityLevel;
use Rector\StaticTypeMapper\TypeFactory\UnionTypeFactory;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
@ -86,18 +88,19 @@ final class TypeFactory
{
// unwrap union types
$unwrappedTypes = [];
foreach ($types as $key => $type) {
if ($type instanceof UnionType) {
$unwrappedTypes = array_merge($unwrappedTypes, $type->getTypes());
foreach ($types as $type) {
$flattenTypes = TypeUtils::flattenTypes($type);
unset($types[$key]);
foreach ($flattenTypes as $flattenType) {
if ($flattenType instanceof ConstantArrayType) {
$unwrappedTypes = array_merge($unwrappedTypes, $this->unwrapConstantArrayTypes($flattenType));
} else {
$unwrappedTypes[] = $flattenType;
}
}
}
$types = array_merge($types, $unwrappedTypes);
// re-index
return array_values($types);
return $unwrappedTypes;
}
/**
@ -137,4 +140,22 @@ final class TypeFactory
return $type;
}
/**
* @return Type[]
*/
private function unwrapConstantArrayTypes(ConstantArrayType $constantArrayType): array
{
$unwrappedTypes = [];
$flattenKeyTypes = TypeUtils::flattenTypes($constantArrayType->getKeyType());
$flattenItemTypes = TypeUtils::flattenTypes($constantArrayType->getItemType());
foreach ($flattenItemTypes as $position => $nestedFlattenItemType) {
$nestedFlattenKeyType = $flattenKeyTypes[$position];
$unwrappedTypes[] = new ArrayType($nestedFlattenKeyType, $nestedFlattenItemType);
}
return $unwrappedTypes;
}
}

View File

@ -30,6 +30,9 @@ final class InterfaceTypeResolverTest extends AbstractNodeTypeResolverTest
$this->assertEquals($expectedTypeWithClassName->getClassName(), $resolvedType->getClassName());
}
/**
* @return Iterator<int[]|string[]|ObjectType[]>
*/
public function dataProvider(): Iterator
{
yield [

View File

@ -27,6 +27,9 @@ final class NameTypeResolverTest extends AbstractNodeTypeResolverTest
$this->assertEquals($expectedType, $resolvedType);
}
/**
* @return Iterator<int[]|string[]|ObjectType[]>
*/
public function provideData(): Iterator
{
$expectedObjectType = new ObjectType(AnotherClass::class);

View File

@ -7,6 +7,7 @@ namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\TraitTypeResolver;
use Iterator;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\AbstractNodeTypeResolverTest;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\TraitTypeResolver\Source\AnotherTrait;
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\TraitTypeResolver\Source\TraitWithTrait;
@ -28,6 +29,9 @@ final class TraitTypeResolverTest extends AbstractNodeTypeResolverTest
$this->assertEquals($expectedType, $resolvedType);
}
/**
* @return Iterator<int[]|string[]|UnionType[]>
*/
public function provideData(): Iterator
{
$unionTypeFactory = new UnionTypeFactory();

View File

@ -88,6 +88,9 @@ final class StaticTypeMapperTest extends AbstractKernelTestCase
$this->assertInstanceOf($expectedType, $phpStanType);
}
/**
* @return Iterator<class-string<IterableType>[]|Identifier[]>
*/
public function provideDataForMapPhpParserNodePHPStanType(): Iterator
{
yield [new Identifier('iterable'), IterableType::class];

View File

@ -41,6 +41,9 @@ final class ArrayTypeComparatorTest extends AbstractKernelTestCase
$this->assertSame($areExpectedEqual, $areEqual);
}
/**
* @return Iterator<ArrayType[]|bool[]>
*/
public function provideData(): Iterator
{
$unionTypeFactory = new UnionTypeFactory();

View File

@ -21,6 +21,7 @@ use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterfac
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedGenericObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\SelfObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
@ -64,7 +65,9 @@ final class ObjectTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMa
}
if ($type instanceof GenericObjectType) {
if (Strings::contains($type->getClassName(), '\\')) {
if ($type instanceof FullyQualifiedGenericObjectType) {
$name = '\\' . $type->getClassName();
} elseif (Strings::contains($type->getClassName(), '\\')) {
$name = '\\' . $type->getClassName();
} else {
$name = $type->getClassName();
@ -103,14 +106,22 @@ final class ObjectTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMa
if ($type instanceof FullyQualifiedObjectType) {
return new FullyQualified($type->getClassName());
}
if (! $type instanceof GenericObjectType) {
// fallback
return new FullyQualified($type->getClassName());
}
if ($type->getClassName() === 'iterable') {
// fallback
return new Name('iterable');
}
if ($type->getClassName() !== 'object') {
// fallback
return new FullyQualified($type->getClassName());
}
return new Name('object');
}

View File

@ -47,6 +47,9 @@ final class ArrayTypeMapperTest extends AbstractKernelTestCase
$this->assertSame($expectedResult, (string) $actualTypeNode);
}
/**
* @return Iterator<string[]|ArrayType[]>
*/
public function provideDataWithoutKeys(): Iterator
{
$arrayType = new ArrayType(new MixedType(), new StringType());

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\StaticTypeMapper\ValueObject\Type;
use PHPStan\Type\Generic\GenericObjectType;
final class FullyQualifiedGenericObjectType extends GenericObjectType
{
}

View File

@ -13,6 +13,9 @@ final class ShortenedObjectType extends ObjectType
*/
private $fullyQualifiedName;
/**
* @param class-string $fullyQualifiedName
*/
public function __construct(string $shortName, string $fullyQualifiedName)
{
parent::__construct($shortName);

View File

@ -48,6 +48,9 @@ final class PhpDocTypeMapperTest extends AbstractKernelTestCase
$this->assertInstanceOf($expectedPHPStanType, $phpStanType);
}
/**
* @return Iterator<class-string<ArrayType>[]|ArrayShapeNode[]>
*/
public function provideData(): Iterator
{
$arrayShapeNode = new ArrayShapeNode([new ArrayShapeItemNode(null, true, new IdentifierTypeNode('string'))]);

View File

@ -628,3 +628,11 @@ parameters:
- '#Cognitive complexity for "Rector\\EarlyReturn\\Rector\\If_\\ChangeAndIfToEarlyReturnRector\:\:refactor\(\)" is 10, keep it under 9#'
- '#Parameter \#2 \$returnedStrictTypeNode of method Rector\\TypeDeclaration\\Rector\\ClassMethod\\ReturnTypeFromStrictTypedCallRector\:\:refactorSingleReturnType\(\) expects PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType, PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PHPStan\\Type\\UnionType given#'
- '#Method Rector\\DowngradePhp80\\Rector\\ClassMethod\\DowngradeTrailingCommasInParamUseRector\:\:processUses\(\) should return PhpParser\\Node\\Expr\\Closure but returns PhpParser\\Node#'
- '#Cognitive complexity for "Rector\\NodeTypeResolver\\NodeTypeResolver\:\:getStaticType\(\)" is 11, keep it under 9#'
-
message: '#There should be no empty class#'
paths:
- packages/static-type-mapper/src/ValueObject/Type/FullyQualifiedGenericObjectType.php
- '#(.*?) class\-string, string given#'

View File

@ -24,6 +24,9 @@ final class MoveEntitiesToEntityDirectoryRectorTest extends AbstractRectorTestCa
$this->assertFileWithContentWasAdded($expectedAddedFileWithContent);
}
/**
* @return Iterator<AddedFileWithContent[]|SmartFileInfo[]>
*/
public function provideData(): Iterator
{
$smartFileSystem = new SmartFileSystem();

View File

@ -103,7 +103,7 @@ CODE_SAMPLE
$firstArgValue = $methodCall->args[0]->value;
foreach ($this->callsWithParamRenames as $callWithParamRename) {
if (! $this->isObjectType($methodCall, $callWithParamRename->getOldObjectType())) {
if (! $this->isObjectType($methodCall->var, $callWithParamRename->getOldObjectType())) {
continue;
}

View File

@ -113,6 +113,7 @@ CODE_SAMPLE
if ($node->stmts === []) {
return null;
}
$haveNodeChanged = false;
foreach ($node->stmts as $key => $stmt) {
if ($stmt instanceof Expression) {
@ -163,7 +164,6 @@ CODE_SAMPLE
}
$propertyFetchType = $this->resolvePropertyFetchType($node->cond);
return $this->staticTypeAnalyzer->isAlwaysTruableType($propertyFetchType);
}

View File

@ -155,11 +155,12 @@ CODE_SAMPLE
foreach ($classMethod->stmts as $statement) {
if ($statement instanceof Return_) {
if (! $statement->expr instanceof Array_) {
$returnedExpr = $statement->expr;
if (! $returnedExpr instanceof Array_) {
continue;
}
return $statement->expr;
return $returnedExpr;
}
}

View File

@ -66,7 +66,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (! $this->isObjectType($node, new ObjectType('Symfony\Component\Console\Style\SymfonyStyle'))) {
if (! $this->isObjectType($node->var, new ObjectType('Symfony\Component\Console\Style\SymfonyStyle'))) {
return null;
}

View File

@ -20,6 +20,9 @@ final class ExtraFilesTest extends AbstractRectorTestCase
$this->doTestFileInfo($originalFileInfo, $extraFileInfos);
}
/**
* @return Iterator<array<int, SmartFileInfo[]>|SmartFileInfo[]>
*/
public function provideData(): Iterator
{
$extraFileInfos = [new SmartFileInfo(__DIR__ . '/Source/UseAbstract.php')];

View File

@ -200,7 +200,7 @@ CODE_SAMPLE
private function matchMethodCall(MethodCall $methodCall): ?ObjectType
{
foreach ($this->callsToFluent as $callToFluent) {
if (! $this->isObjectType($methodCall, $callToFluent->getObjectType())) {
if (! $this->isObjectType($methodCall->var, $callToFluent->getObjectType())) {
continue;
}

View File

@ -159,14 +159,11 @@ CODE_SAMPLE
private function matchReturnMethodCall(Return_ $return): ?MethodCall
{
if ($return->expr === null) {
$returnExpr = $return->expr;
if (! $returnExpr instanceof MethodCall) {
return null;
}
if (! $return->expr instanceof MethodCall) {
return null;
}
return $return->expr;
return $returnExpr;
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\Defluent\Rector\Return_;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\Return_;
use Rector\Core\Rector\AbstractRector;
@ -108,7 +109,7 @@ CODE_SAMPLE
return null;
}
private function matchReturnMethodCall(Return_ $return): ?MethodCall
private function matchReturnMethodCall(Return_ $return): ?Expr
{
if ($return->expr === null) {
return null;

View File

@ -55,8 +55,10 @@ abstract class AbstractRootExpr implements RootExprAwareInterface, FirstCallFact
return null;
}
if ($currentStmt->expr instanceof Assign) {
return $currentStmt->expr;
$currentExpr = $currentStmt->expr;
if ($currentExpr instanceof Assign) {
return $currentExpr;
}
return null;

View File

@ -16,6 +16,7 @@ use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Type\ObjectType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Defluent\NodeAnalyzer\FluentChainMethodCallNodeAnalyzer;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
@ -32,9 +33,17 @@ final class ChangeSetParametersArrayToArrayCollectionRector extends AbstractRect
*/
private $arrayTypeAnalyzer;
public function __construct(ArrayTypeAnalyzer $arrayTypeAnalyzer)
{
/**
* @var FluentChainMethodCallNodeAnalyzer
*/
private $fluentChainMethodCallNodeAnalyzer;
public function __construct(
ArrayTypeAnalyzer $arrayTypeAnalyzer,
FluentChainMethodCallNodeAnalyzer $fluentChainMethodCallNodeAnalyzer
) {
$this->arrayTypeAnalyzer = $arrayTypeAnalyzer;
$this->fluentChainMethodCallNodeAnalyzer = $fluentChainMethodCallNodeAnalyzer;
}
/**
@ -131,16 +140,22 @@ CODE_SAMPLE
return true;
}
//one of the cases when we are in the repo and it's extended from EntityRepository
// one of the cases when we are in the repo and it's extended from EntityRepository
if (! $this->isObjectType($classLike, new ObjectType('Doctrine\ORM\EntityRepository'))) {
return true;
}
if (! $this->isObjectType($methodCall->var, new ObjectType('Doctrine\ORM\EntityRepository'))) {
if (! $this->isName($methodCall->name, 'setParameters')) {
return true;
}
return ! $this->isName($methodCall->name, 'setParameters');
// compare root variable
$rootExpr = $this->fluentChainMethodCallNodeAnalyzer->resolveRootMethodCall($methodCall);
if (! $rootExpr instanceof MethodCall) {
return true;
}
return ! $this->isObjectType($rootExpr, new ObjectType('Doctrine\ORM\QueryBuilder'));
}
private function getNewArrayCollectionFromSetParametersArgument(Arg $arg): New_

View File

@ -114,7 +114,10 @@ CODE_SAMPLE
public function refactor(Node $node): ?Node
{
foreach ($this->typeToTimeMethodsAndPositions as $typeToTimeMethodAndPosition) {
if (! $this->isObjectType($node, $typeToTimeMethodAndPosition->getObjectType())) {
if (! $this->isObjectType(
$node instanceof MethodCall ? $node->var : $node->class,
$typeToTimeMethodAndPosition->getObjectType()
)) {
continue;
}

View File

@ -120,7 +120,7 @@ CODE_SAMPLE
private function shouldSkip(Node $node): bool
{
if ($node instanceof StaticCall) {
return ! $this->nodeTypeResolver->isObjectTypes($node, $this->requestObjectTypes);
return ! $this->nodeTypeResolver->isObjectTypes($node->class, $this->requestObjectTypes);
}
$classLike = $node->getAttribute(AttributeKey::CLASS_NODE);

View File

@ -116,13 +116,17 @@ final class SingletonClassMethodAnalyzer
return $expr->left;
}
}
// matching: "! self::$static"
if (! $expr instanceof BooleanNot) {
return null;
}
if (! $expr->expr instanceof StaticPropertyFetch) {
$negatedExpr = $expr->expr;
if (! $negatedExpr instanceof StaticPropertyFetch) {
return null;
}
return $expr->expr;
return $negatedExpr;
}
}

View File

@ -72,6 +72,9 @@ final class PropertyRenameFactoryTest extends AbstractKernelTestCase
$this->assertSame($currentName, $actualPropertyRename->getCurrentName());
}
/**
* @return Iterator<string[]|SmartFileInfo[]>
*/
public function provideData(): Iterator
{
yield [new SmartFileInfo(__DIR__ . '/Fixture/skip_some_class.php.inc'), 'eliteManager', 'eventManager'];

View File

@ -85,12 +85,12 @@ CODE_SAMPLE
return null;
}
if (! $this->isObjectType($node, new ObjectType('Tester\TestCase'))) {
if ($node instanceof MethodCall) {
$this->processUnderTestRun($node);
return null;
}
if ($node instanceof MethodCall) {
$this->processUnderTestRun($node);
if (! $this->isObjectType($node, new ObjectType('Tester\TestCase'))) {
return null;
}
@ -110,6 +110,10 @@ CODE_SAMPLE
private function processUnderTestRun(MethodCall $methodCall): void
{
if (! $this->isObjectType($methodCall->var, new ObjectType('Tester\TestCase'))) {
return;
}
if ($this->isName($methodCall->name, 'run')) {
$this->removeNode($methodCall);
}

View File

@ -13,7 +13,6 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeWithClassName;
use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Symfony\SymfonyRouteTagValueNode;
use Rector\BetterPhpDocParser\ValueObjectFactory\PhpDocNode\Symfony\SymfonyRouteTagValueNodeFactory;
use Rector\Core\Rector\AbstractRector;
@ -229,7 +228,7 @@ CODE_SAMPLE
if ($node->expr instanceof StaticCall) {
// for custom static route factories
return $this->isRouteStaticCallMatch($node->expr);
return $this->nodeTypeResolver->isObjectType($node->expr, new ObjectType('Nette\Application\IRouter'));
}
return false;
@ -298,39 +297,6 @@ CODE_SAMPLE
}
}
private function isRouteStaticCallMatch(StaticCall $staticCall): bool
{
$className = $this->getName($staticCall->class);
if ($className === null) {
return false;
}
$methodName = $this->getName($staticCall->name);
if ($methodName === null) {
return false;
}
if (! $this->reflectionProvider->hasClass($className)) {
return false;
}
$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->hasMethod($methodName)) {
return false;
}
$methodReflection = $classReflection->getNativeMethod($methodName);
$parametersAcceptor = $methodReflection->getVariants()[0];
$returnType = $parametersAcceptor->getReturnType();
if ($returnType instanceof TypeWithClassName) {
return is_a($returnType->getClassName(), 'Nette\Application\IRouter', true);
}
return false;
}
private function shouldSkipClassMethod(ClassMethod $classMethod): bool
{
// not an action method

View File

@ -67,7 +67,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (! $this->isObjectType($node, new ObjectType('Nette\Application\Request'))) {
if (! $this->isObjectType($node->var, new ObjectType('Nette\Application\Request'))) {
return null;
}

View File

@ -14,7 +14,7 @@ final class ConstantReferenceRouterFactory
$routeList = new RouteList();
// case of single action controller, usually get() or __invoke() method
$routeList[] = Route::get(self::SOME_PATH, ConstantReferenceSomePresenter::class);
$routeList[] = new Route(self::SOME_PATH, ConstantReferenceSomePresenter::class);
return $routeList;
}
@ -51,7 +51,7 @@ final class ConstantReferenceRouterFactory
final class ConstantReferenceSomePresenter
{
/**
* @\Symfony\Component\Routing\Annotation\Route(path="/some-path", methods={"GET"})
* @\Symfony\Component\Routing\Annotation\Route(path="/some-path")
*/
public function run()
{

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\Nette\Rector\NotIdentical;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
@ -86,25 +87,27 @@ CODE_SAMPLE
return $containsStaticCall;
}
private function matchStrposInComparisonToFalse(BinaryOp $binaryOp): ?FuncCall
private function matchStrposInComparisonToFalse(BinaryOp $binaryOp): ?Expr
{
if ($this->valueResolver->isFalse($binaryOp->left)) {
if (! $binaryOp->right instanceof FuncCall) {
$rightExpr = $binaryOp->right;
if (! $rightExpr instanceof FuncCall) {
return null;
}
if ($this->isName($binaryOp->right, 'strpos')) {
return $binaryOp->right;
if ($this->isName($rightExpr, 'strpos')) {
return $rightExpr;
}
}
if ($this->valueResolver->isFalse($binaryOp->right)) {
if (! $binaryOp->left instanceof FuncCall) {
$leftExpr = $binaryOp->left;
if (! $leftExpr instanceof FuncCall) {
return null;
}
if ($this->isName($binaryOp->left, 'strpos')) {
return $binaryOp->left;
if ($this->isName($leftExpr, 'strpos')) {
return $leftExpr;
}
}

View File

@ -4,12 +4,13 @@ declare(strict_types=1);
namespace Rector\Restoration\Type;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
@ -27,18 +28,37 @@ final class ConstantReturnToParamTypeConverter
public function convert(Type $type): Type
{
if ($type instanceof UnionType) {
$flattenReturnTypes = TypeUtils::flattenTypes($type);
$unionedTypes = [];
foreach ($flattenReturnTypes as $flattenReturnType) {
if ($flattenReturnType instanceof ArrayType) {
$unionedTypes[] = $flattenReturnType->getItemType();
}
}
$resolvedTypes = [];
foreach ($unionedTypes as $unionedType) {
$resolvedTypes[] = $this->convert($unionedType);
}
return new UnionType($resolvedTypes);
}
if ($type instanceof ConstantStringType) {
return $this->unwrapConstantTypeToObjectType($type);
}
if ($type instanceof ConstantArrayType) {
if ($type instanceof ArrayType) {
return $this->unwrapConstantTypeToObjectType($type);
}
return new MixedType();
}
private function unwrapConstantTypeToObjectType(Type $type): Type
{
if ($type instanceof ConstantArrayType) {
if ($type instanceof ArrayType) {
return $this->unwrapConstantTypeToObjectType($type->getItemType());
}

View File

@ -53,7 +53,10 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (! $this->isObjectType($node, new ObjectType('Symfony\Component\DependencyInjection\ContainerBuilder'))) {
if (! $this->isObjectType(
$node->var,
new ObjectType('Symfony\Component\DependencyInjection\ContainerBuilder')
)) {
return null;
}

View File

@ -67,7 +67,7 @@ CODE_SAMPLE
public function refactor(Node $node): ?Node
{
foreach ($this->methodCallRenamesWithAddedArguments as $methodCallRenameWithAddedArgument) {
if (! $this->isObjectType($node, $methodCallRenameWithAddedArgument->getObjectType())) {
if (! $this->isObjectType($node->var, $methodCallRenameWithAddedArgument->getObjectType())) {
continue;
}

View File

@ -67,7 +67,7 @@ final class StaticCallToFuncCallRector extends AbstractRector implements Configu
public function refactor(Node $node): ?Node
{
foreach ($this->staticCallsToFunctions as $staticCallToFunction) {
if (! $this->isObjectType($node, $staticCallToFunction->getObjectType())) {
if (! $this->isObjectType($node->class, $staticCallToFunction->getObjectType())) {
continue;
}

View File

@ -99,7 +99,7 @@ CODE_SAMPLE
private function processMethodCall(MethodCall $methodCall): ?Node
{
foreach ($this->methodNamesByType as $type => $methodName) {
if (! $this->isObjectType($methodCall, new ObjectType($type))) {
if (! $this->isObjectType($methodCall->var, new ObjectType($type))) {
continue;
}

View File

@ -22,6 +22,9 @@ final class CommunityTestCaseRectorTest extends AbstractRectorTestCase
$this->assertFileWithContentWasAdded($addedFileWithContent);
}
/**
* @return Iterator<AddedFileWithContent[]|SmartFileInfo[]>
*/
public function provideData(): Iterator
{
$smartFileSystem = new SmartFileSystem();

View File

@ -21,6 +21,9 @@ final class NativeTestCaseRectorTest extends AbstractRectorTestCase
$this->assertFileWithContentWasAdded($addedFileWithContent);
}
/**
* @return Iterator<AddedFileWithContent[]|SmartFileInfo[]>
*/
public function provideData(): Iterator
{
$smartFileSystem = new SmartFileSystem();

View File

@ -8,12 +8,11 @@ use Symplify\SymfonyPhpConfig\ValueObjectInliner;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(StaticCallToFuncCallRector::class)
->call('configure', [[
StaticCallToFuncCallRector::STATIC_CALLS_TO_FUNCTIONS => ValueObjectInliner::inline([
new StaticCallToFuncCall(SomeOldStaticClass::class, 'render', 'view'),
]),
]]);
};

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\NodeTypeAnalyzer;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
final class DetailedTypeAnalyzer
{
/**
* @var int
*/
private const MAX_NUMBER_OF_TYPES = 3;
public function isTooDetailed(Type $type): bool
{
if ($type instanceof UnionType) {
return count($type->getTypes()) > self::MAX_NUMBER_OF_TYPES;
}
if ($type instanceof ConstantArrayType) {
return count($type->getValueTypes()) > self::MAX_NUMBER_OF_TYPES;
}
if ($type instanceof GenericObjectType) {
return $this->isTooDetailedGenericObjectType($type);
}
return false;
}
private function isTooDetailedGenericObjectType(GenericObjectType $genericObjectType): bool
{
if (count($genericObjectType->getTypes()) !== 1) {
return false;
}
$genericType = $genericObjectType->getTypes()[0];
return $this->isTooDetailed($genericType);
}
}

View File

@ -9,9 +9,11 @@ use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Rector\AbstractRector;
use Rector\DeadDocBlock\TagRemover\ParamTagRemover;
use Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@ -33,10 +35,19 @@ final class AddArrayParamDocTypeRector extends AbstractRector
*/
private $phpDocTypeChanger;
public function __construct(ParamTypeInferer $paramTypeInferer, PhpDocTypeChanger $phpDocTypeChanger)
{
/**
* @var ParamTagRemover
*/
private $paramTagRemover;
public function __construct(
ParamTypeInferer $paramTypeInferer,
PhpDocTypeChanger $phpDocTypeChanger,
ParamTagRemover $paramTagRemover
) {
$this->paramTypeInferer = $paramTypeInferer;
$this->phpDocTypeChanger = $phpDocTypeChanger;
$this->paramTagRemover = $paramTagRemover;
}
public function getRuleDefinition(): RuleDefinition
@ -105,17 +116,17 @@ CODE_SAMPLE
continue;
}
$type = $this->paramTypeInferer->inferParam($param);
if ($type instanceof MixedType) {
$paramType = $this->paramTypeInferer->inferParam($param);
if ($paramType instanceof MixedType) {
continue;
}
$paramName = $this->getName($param);
$this->phpDocTypeChanger->changeParamType($phpDocInfo, $type, $param, $paramName);
$this->phpDocTypeChanger->changeParamType($phpDocInfo, $paramType, $param, $paramName);
}
if ($phpDocInfo->hasChanged()) {
$this->paramTagRemover->removeParamTagsIfUseless($phpDocInfo, $node);
return $node;
}
@ -130,28 +141,34 @@ CODE_SAMPLE
}
// not an array type
if (! $this->isName($param->type, 'array')) {
$paramType = $this->nodeTypeResolver->resolve($param->type);
// weird case for maybe interface
if ($paramType->isIterable()->maybe() && ($paramType instanceof ObjectType)) {
return true;
}
// not an array type
$paramStaticType = $this->getStaticType($param);
if ($paramStaticType instanceof MixedType) {
$isArrayable = $paramType->isIterable()
->yes() || $paramType->isArray()
->yes() || ($paramType->isIterable()->maybe() || $paramType->isArray()->maybe());
if (! $isArrayable) {
return true;
}
return $this->isArrayExplicitMixed($paramType);
}
private function isArrayExplicitMixed(Type $type): bool
{
if (! $type instanceof ArrayType) {
return false;
}
if (! $paramStaticType instanceof ArrayType) {
return true;
$iterableValueType = $type->getIterableValueType();
if (! $iterableValueType instanceof MixedType) {
return false;
}
if (! $paramStaticType->getIterableValueType() instanceof MixedType) {
return true;
}
// is defined mixed[] explicitly
/** @var MixedType $mixedType */
$mixedType = $paramStaticType->getIterableValueType();
return $mixedType->isExplicitMixed();
return $iterableValueType->isExplicitMixed();
}
}

View File

@ -10,18 +10,18 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\VoidType;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareArrayShapeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareGenericTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Rector\AbstractRector;
use Rector\DeadDocBlock\TagRemover\ReturnTagRemover;
use Rector\Privatization\TypeManipulator\NormalizeTypeToRespectArrayScalarType;
use Rector\TypeDeclaration\NodeTypeAnalyzer\DetailedTypeAnalyzer;
use Rector\TypeDeclaration\TypeAnalyzer\AdvancedArrayAnalyzer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTypeDeclarationReturnTypeInferer;
@ -36,11 +36,6 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
*/
final class AddArrayReturnDocTypeRector extends AbstractRector
{
/**
* @var int
*/
private const MAX_NUMBER_OF_TYPES = 3;
/**
* @var ReturnTypeInferer
*/
@ -66,18 +61,32 @@ final class AddArrayReturnDocTypeRector extends AbstractRector
*/
private $normalizeTypeToRespectArrayScalarType;
/**
* @var ReturnTagRemover
*/
private $returnTagRemover;
/**
* @var DetailedTypeAnalyzer
*/
private $detailedTypeAnalyzer;
public function __construct(
ReturnTypeInferer $returnTypeInferer,
ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard,
AdvancedArrayAnalyzer $advancedArrayAnalyzer,
PhpDocTypeChanger $phpDocTypeChanger,
NormalizeTypeToRespectArrayScalarType $normalizeTypeToRespectArrayScalarType
NormalizeTypeToRespectArrayScalarType $normalizeTypeToRespectArrayScalarType,
ReturnTagRemover $returnTagRemover,
DetailedTypeAnalyzer $detailedTypeAnalyzer
) {
$this->returnTypeInferer = $returnTypeInferer;
$this->classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard;
$this->advancedArrayAnalyzer = $advancedArrayAnalyzer;
$this->phpDocTypeChanger = $phpDocTypeChanger;
$this->normalizeTypeToRespectArrayScalarType = $normalizeTypeToRespectArrayScalarType;
$this->returnTagRemover = $returnTagRemover;
$this->detailedTypeAnalyzer = $detailedTypeAnalyzer;
}
public function getRuleDefinition(): RuleDefinition
@ -136,6 +145,7 @@ CODE_SAMPLE
public function refactor(Node $node): ?Node
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
if ($this->shouldSkip($node, $phpDocInfo)) {
return null;
}
@ -150,6 +160,10 @@ CODE_SAMPLE
$node->returnType
);
if ($this->detailedTypeAnalyzer->isTooDetailed($inferredReturnType)) {
return null;
}
$currentReturnType = $phpDocInfo->getReturnType();
if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethodOldTypeWithNewType(
$currentReturnType,
@ -163,6 +177,7 @@ CODE_SAMPLE
}
$this->phpDocTypeChanger->changeReturnType($phpDocInfo, $inferredReturnType);
$this->returnTagRemover->removeReturnTagIfUseless($phpDocInfo, $node);
return $node;
}
@ -200,7 +215,7 @@ CODE_SAMPLE
return true;
}
if ($newType instanceof UnionType && $this->shouldSkipUnionType($newType)) {
if ($this->detailedTypeAnalyzer->isTooDetailed($newType)) {
return true;
}
@ -213,11 +228,7 @@ CODE_SAMPLE
return true;
}
if (! $newType instanceof ConstantArrayType) {
return false;
}
return count($newType->getValueTypes()) > self::MAX_NUMBER_OF_TYPES;
return $this->detailedTypeAnalyzer->isTooDetailed($newType);
}
private function shouldSkipClassMethod(ClassMethod $classMethod): bool
@ -230,7 +241,7 @@ CODE_SAMPLE
return false;
}
return ! $this->isNames($classMethod->returnType, ['array', 'iterable']);
return ! $this->isNames($classMethod->returnType, ['array', 'iterable', 'Iterator']);
}
private function hasArrayShapeNode(ClassMethod $classMethod): bool
@ -275,9 +286,4 @@ CODE_SAMPLE
return $this->advancedArrayAnalyzer->isMixedOfSpecificOverride($arrayType, $phpDocInfo);
}
private function shouldSkipUnionType(UnionType $unionType): bool
{
return count($unionType->getTypes()) > self::MAX_NUMBER_OF_TYPES;
}
}

View File

@ -13,7 +13,6 @@ use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\Rector\AbstractRector;
@ -24,6 +23,7 @@ use Rector\TypeDeclaration\ChildPopulator\ChildReturnPopulator;
use Rector\TypeDeclaration\PhpDocParser\NonInformativeReturnTagRemover;
use Rector\TypeDeclaration\PhpParserTypeAnalyzer;
use Rector\TypeDeclaration\TypeAlreadyAddedChecker\ReturnTypeAlreadyAddedChecker;
use Rector\TypeDeclaration\TypeAnalyzer\ObjectTypeComparator;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTypeDeclarationReturnTypeInferer;
use Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnTypeOverrideGuard;
@ -77,6 +77,11 @@ final class ReturnTypeDeclarationRector extends AbstractRector
*/
private $phpParserTypeAnalyzer;
/**
* @var ObjectTypeComparator
*/
private $objectTypeComparator;
public function __construct(
ReturnTypeInferer $returnTypeInferer,
ChildReturnPopulator $childReturnPopulator,
@ -84,7 +89,8 @@ final class ReturnTypeDeclarationRector extends AbstractRector
NonInformativeReturnTagRemover $nonInformativeReturnTagRemover,
ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard,
VendorLockResolver $vendorLockResolver,
PhpParserTypeAnalyzer $phpParserTypeAnalyzer
PhpParserTypeAnalyzer $phpParserTypeAnalyzer,
ObjectTypeComparator $objectTypeComparator
) {
$this->returnTypeInferer = $returnTypeInferer;
$this->returnTypeAlreadyAddedChecker = $returnTypeAlreadyAddedChecker;
@ -93,6 +99,7 @@ final class ReturnTypeDeclarationRector extends AbstractRector
$this->classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard;
$this->vendorLockResolver = $vendorLockResolver;
$this->phpParserTypeAnalyzer = $phpParserTypeAnalyzer;
$this->objectTypeComparator = $objectTypeComparator;
}
/**
@ -241,7 +248,7 @@ CODE_SAMPLE
}
$currentType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($functionLike->returnType);
if ($this->isCurrentObjectTypeSubType($currentType, $inferedType)) {
if ($this->objectTypeComparator->isCurrentObjectTypeSubType($currentType, $inferedType)) {
return true;
}
@ -272,22 +279,6 @@ CODE_SAMPLE
}
}
/**
* E.g. current E, new type A, E extends A true
*/
private function isCurrentObjectTypeSubType(Type $currentType, Type $inferedType): bool
{
if (! $currentType instanceof ObjectType) {
return false;
}
if (! $inferedType instanceof ObjectType) {
return false;
}
return is_a($currentType->getClassName(), $inferedType->getClassName(), true);
}
private function isNullableTypeSubType(Type $currentType, Type $inferedType): bool
{
if (! $currentType instanceof UnionType) {

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeAnalyzer;
use PHPStan\Type\CallableType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
final class ObjectTypeComparator
{
/**
* E.g. current E, new type A, E extends A true
* Also for closure/callable, iterable/Traversable/Iterator/Generator
*/
public function isCurrentObjectTypeSubType(Type $currentType, Type $newType): bool
{
if ($this->isBothCallable($currentType, $newType)) {
return true;
}
if ($this->isBothIterableIteratorGeneratorTraversable($currentType, $newType)) {
return true;
}
if (! $currentType instanceof ObjectType) {
return false;
}
if (! $newType instanceof ObjectType) {
return false;
}
return is_a($currentType->getClassName(), $newType->getClassName(), true);
}
private function isClosure(Type $type): bool
{
return $type instanceof ObjectType && $type->getClassName() === 'Closure';
}
private function isBothCallable(Type $currentType, Type $newType): bool
{
if ($currentType instanceof CallableType && $this->isClosure($newType)) {
return true;
}
return $newType instanceof CallableType && $this->isClosure($currentType);
}
private function isBothIterableIteratorGeneratorTraversable(Type $currentType, Type $newType): bool
{
if (! $currentType instanceof ObjectType) {
return false;
}
if (! $newType instanceof ObjectType) {
return false;
}
if ($currentType->getClassName() === 'iterable' && $this->isTraversableGeneratorIterator($newType)) {
return true;
}
if ($newType->getClassName() !== 'iterable') {
return false;
}
return $this->isTraversableGeneratorIterator($currentType);
}
private function isTraversableGeneratorIterator(ObjectType $objectType): bool
{
return in_array($objectType->getClassName(), ['Traversable', 'Generator', 'Iterator'], true);
}
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\TypeDeclaration\Contract\TypeInferer\ParamTypeInfererInterface;
final class KnownArrayParamTypeInferer implements ParamTypeInfererInterface
{
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(NodeNameResolver $nodeNameResolver, ReflectionProvider $reflectionProvider)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
}
public function inferParam(Param $param): Type
{
$classLike = $param->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return new MixedType();
}
$className = $this->nodeNameResolver->getName($classLike);
if (! $className) {
return new MixedType();
}
if (! $this->reflectionProvider->hasClass($className)) {
return new MixedType();
}
$classReflection = $this->reflectionProvider->getClass($className);
$paramName = $this->nodeNameResolver->getName($param);
// @todo create map later
if ($paramName === 'configs' && $classReflection->isSubclassOf(
'Symfony\Component\DependencyInjection\Extension\Extension'
)) {
return new ArrayType(new MixedType(), new StringType());
}
return new MixedType();
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
use PhpParser\Node\Param;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\TypeDeclaration\Contract\TypeInferer\ParamTypeInfererInterface;
use Rector\TypeDeclaration\TypeInferer\SplArrayFixedTypeNarrower;
final class SplFixedArrayParamTypeInferer implements ParamTypeInfererInterface
{
/**
* @var SplArrayFixedTypeNarrower
*/
private $splArrayFixedTypeNarrower;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(
SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower,
NodeTypeResolver $nodeTypeResolver
) {
$this->splArrayFixedTypeNarrower = $splArrayFixedTypeNarrower;
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function inferParam(Param $param): Type
{
if ($param->type === null) {
return new MixedType();
}
$paramType = $this->nodeTypeResolver->resolve($param->type);
return $this->splArrayFixedTypeNarrower->narrow($paramType);
}
}

View File

@ -24,6 +24,7 @@ use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver;
use Rector\TypeDeclaration\TypeInferer\SplArrayFixedTypeNarrower;
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
@ -53,16 +54,23 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
*/
private $typeFactory;
/**
* @var SplArrayFixedTypeNarrower
*/
private $splArrayFixedTypeNarrower;
public function __construct(
SilentVoidResolver $silentVoidResolver,
NodeTypeResolver $nodeTypeResolver,
SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
TypeFactory $typeFactory
TypeFactory $typeFactory,
SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower
) {
$this->silentVoidResolver = $silentVoidResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
$this->typeFactory = $typeFactory;
$this->splArrayFixedTypeNarrower = $splArrayFixedTypeNarrower;
}
/**
@ -90,13 +98,10 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface
$hasSilentVoid = $this->silentVoidResolver->hasSilentVoid($functionLike);
foreach ($localReturnNodes as $localReturnNode) {
if ($localReturnNode->expr === null) {
$this->types[] = new VoidType();
continue;
}
$returnedExprType = $this->nodeTypeResolver->getStaticType($localReturnNode);
$returnedExprType = $this->splArrayFixedTypeNarrower->narrow($returnedExprType);
$exprType = $this->nodeTypeResolver->getStaticType($localReturnNode->expr);
$this->types[] = $exprType;
$this->types[] = $returnedExprType;
}
if ($hasSilentVoid) {

View File

@ -13,25 +13,16 @@ use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\NodeTraverser;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedGenericObjectType;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
final class YieldNodesReturnTypeInferer implements ReturnTypeInfererInterface
{
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;
/**
* @var NodeTypeResolver
*/
@ -48,12 +39,10 @@ final class YieldNodesReturnTypeInferer implements ReturnTypeInfererInterface
private $simpleCallableNodeTraverser;
public function __construct(
PhpVersionProvider $phpVersionProvider,
NodeTypeResolver $nodeTypeResolver,
TypeFactory $typeFactory,
SimpleCallableNodeTraverser $simpleCallableNodeTraverser
) {
$this->phpVersionProvider = $phpVersionProvider;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->typeFactory = $typeFactory;
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
@ -77,18 +66,11 @@ final class YieldNodesReturnTypeInferer implements ReturnTypeInfererInterface
continue;
}
$yieldValueStaticType = $this->nodeTypeResolver->getStaticType($value);
$types[] = new ArrayType(new MixedType(), $yieldValueStaticType);
$types[] = $this->nodeTypeResolver->getStaticType($value);
}
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::ITERABLE_TYPE)) {
// @see https://www.php.net/manual/en/language.types.iterable.php
$types[] = new IterableType(new MixedType(), new MixedType());
} else {
$types[] = new ObjectType('Iterator');
}
return $this->typeFactory->createMixedPassedOrUnionType($types);
$types = $this->typeFactory->createMixedPassedOrUnionType($types);
return new FullyQualifiedGenericObjectType('Iterator', [$types]);
}
public function getPriority(): int

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\TypeInferer;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
final class SplArrayFixedTypeNarrower
{
public function narrow(Type $paramType): Type
{
if ($paramType->isSuperTypeOf(new ObjectType('SplArrayFixed'))->no()) {
return $paramType;
}
if (! $paramType instanceof TypeWithClassName) {
return $paramType;
}
if ($paramType instanceof GenericObjectType) {
return $paramType;
}
$types = [];
if ($paramType->getClassName() === 'PhpCsFixer\Tokenizer\Tokens') {
$types[] = new ObjectType('PhpCsFixer\Tokenizer\Token');
}
if ($paramType->getClassName() === 'PhpCsFixer\Doctrine\Annotation\Tokens') {
$types[] = new ObjectType('PhpCsFixer\Doctrine\Annotation\Token');
}
if ($types === []) {
return $paramType;
}
return new GenericObjectType($paramType->getClassName(), $types);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
final class KnownParamArray extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
final class KnownParamArray extends Extension
{
/**
* @param string[] $configs
*/
public function load(array $configs, ContainerBuilder $container)
{
}
}
?>

View File

@ -0,0 +1,21 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
final class SkipAlreadyParam
{
/**
* @var string|null
*/
private $rectorClass;
public function setRectorClass(string $rectorClass)
{
$this->rectorClass = $rectorClass;
}
public function getRectorClass(): ?string
{
return $this->rectorClass;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use PhpParser\Node;
final class SkipSetNode
{
/**
* @var Node|null
*/
private $node;
public function setNode(Node $node): void
{
$this->node = $node;
}
public function getNode(): ?Node
{
return $this->node;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use PhpCsFixer\Tokenizer\Tokens;
final class TokenPhpCsFixer
{
public function __construct(Tokens $tokens)
{
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use PhpCsFixer\Tokenizer\Tokens;
final class TokenPhpCsFixer
{
/**
* @param \PhpCsFixer\Tokenizer\Tokens<\PhpCsFixer\Tokenizer\Token> $tokens
*/
public function __construct(Tokens $tokens)
{
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use PhpCsFixer\Doctrine\Annotation\Tokens;
final class TokenPhpCsFixerDoctrine
{
public function __construct(Tokens $tokens)
{
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayParamDocTypeRector\Fixture;
use PhpCsFixer\Doctrine\Annotation\Tokens;
final class TokenPhpCsFixerDoctrine
{
/**
* @param \PhpCsFixer\Doctrine\Annotation\Tokens<\PhpCsFixer\Doctrine\Annotation\Token> $tokens
*/
public function __construct(Tokens $tokens)
{
}
}
?>

View File

@ -36,7 +36,7 @@ final class RespectArrayReturnType
private $items = [];
/**
* @return mixed[]|array<string, int|mixed[]>
* @return mixed[]|array<string, mixed[]>|array<string, int>
*/
public function process($message): array
{

View File

@ -0,0 +1,34 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use PhpCsFixer\Tokenizer\Tokens;
final class ReturnTokens
{
public function createTokens()
{
return new Tokens();
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use PhpCsFixer\Tokenizer\Tokens;
final class ReturnTokens
{
/**
* @return \PhpCsFixer\Tokenizer\Tokens<\PhpCsFixer\Tokenizer\Token>
*/
public function createTokens()
{
return new Tokens();
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
final class ReturnYieldIterator
{
public function someMethod()
{
yield ['test', 'test2'];
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
final class ReturnYieldIterator
{
/**
* @return \Iterator<string[]>
*/
public function someMethod()
{
yield ['test', 'test2'];
}
}
?>

View File

@ -0,0 +1,17 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Iterator;
final class SkipBareIterator
{
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
private function yieldFilesFromDirectory(string $string): Iterator
{
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use PhpParser\Node\Stmt;
use PhpParser\Parser as NikicParser;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
final class SkipEmptyArray
{
/**
* @var Stmt[][]
*/
private $nodesByFile = [];
/**
* @var NikicParser
*/
private $nikicParser;
/**
* @var SmartFileSystem
*/
private $smartFileSystem;
public function __construct(NikicParser $nikicParser, SmartFileSystem $smartFileSystem)
{
$this->nikicParser = $nikicParser;
$this->smartFileSystem = $smartFileSystem;
}
/**
* @return Stmt[]
*/
public function parseFileInfo(SmartFileInfo $smartFileInfo): array
{
$fileRealPath = $smartFileInfo->getRealPath();
if (isset($this->nodesByFile[$fileRealPath])) {
return $this->nodesByFile[$fileRealPath];
}
$fileContent = $this->smartFileSystem->readFile($fileRealPath);
$nodes = $this->nikicParser->parse($fileContent);
if ($nodes === null) {
$this->nodesByFile[$fileRealPath] = [];
} else {
$this->nodesByFile[$fileRealPath] = [];
}
return $this->nodesByFile[$fileRealPath];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Symplify\SmartFileSystem\SmartFileInfo;
final class SkipReturnArrayMerge
{
/**
* @var array<string, SmartFileInfo[]>
*/
private $resolvedConfigFileInfos = [];
/**
* @param SmartFileInfo[] $setFileInfos
* @return SmartFileInfo[]
*/
public function run(SmartFileInfo $configFileInfo): array
{
$hash = sha1($configFileInfo->getRealPath());
if (isset($this->resolvedConfigFileInfos[$hash])) {
return $this->resolvedConfigFileInfos[$hash];
}
return [];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Iterator;
final class SkipTooManyIterator
{
public function provide(): Iterator
{
yield [
__DIR__ . '/config/main_config_with_only_imports.php', [
'old_2' => 'new_2',
'old_1' => 'new_1',
],
];
yield [
__DIR__ . '/config/one_set_with_own_rename.php', [
'PHPUnit_Framework_MockObject_Stub' => 'PHPUnit\Framework\MockObject\Stub',
'PHPUnit_Framework_MockObject_Stub_Return' => 'PHPUnit\Framework\MockObject\Stub\ReturnStub',
'PHPUnit_Framework_MockObject_MockObject' => 'PHPUnit\Framework\MockObject\MockObject',
'Old' => 'New',
],
];
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Iterator;
use Symplify\SmartFileSystem\SmartFileInfo;
final class SomeIterator
{
public function someMethod(): Iterator
{
return self::someIterator();
}
/**
* @return \Iterator<array<int, SmartFileInfo>>
*/
public static function someIterator(): Iterator
{
yield [100 => new SmartFileInfo('...')];
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Iterator;
use Symplify\SmartFileSystem\SmartFileInfo;
final class SomeIterator
{
/**
* @return Iterator<mixed, \Symplify\SmartFileSystem\SmartFileInfo[]>
*/
public function someMethod(): Iterator
{
return self::someIterator();
}
/**
* @return \Iterator<array<int, SmartFileInfo>>
*/
public static function someIterator(): Iterator
{
yield [100 => new SmartFileInfo('...')];
}
}
?>

View File

@ -0,0 +1,72 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Iterator;
use Rector\FileSystemRector\ValueObject\AddedFileWithContent;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
final class UniqueReturnIterator
{
public function provideData(): Iterator
{
$smartFileSystem = new SmartFileSystem();
yield [
new SmartFileInfo(__DIR__ . '/Source/Entity/RandomInterface.php'),
new AddedFileWithContent(
'/Source/Contract/RandomInterface.php',
$smartFileSystem->readFile(__DIR__ . '/Expected/ExpectedRandomInterface.php')
)
];
yield [
new SmartFileInfo(__DIR__ . '/Source/Control/ControlFactory.php'),
new AddedFileWithContent(
'/Source/Control/ControlFactory.php',
$smartFileSystem->readFile(__DIR__ . '/Source/Control/ControlFactory.php')
),
];
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture;
use Iterator;
use Rector\FileSystemRector\ValueObject\AddedFileWithContent;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
final class UniqueReturnIterator
{
/**
* @return \Iterator<\Rector\FileSystemRector\ValueObject\AddedFileWithContent[]|\Symplify\SmartFileSystem\SmartFileInfo[]>
*/
public function provideData(): Iterator
{
$smartFileSystem = new SmartFileSystem();
yield [
new SmartFileInfo(__DIR__ . '/Source/Entity/RandomInterface.php'),
new AddedFileWithContent(
'/Source/Contract/RandomInterface.php',
$smartFileSystem->readFile(__DIR__ . '/Expected/ExpectedRandomInterface.php')
)
];
yield [
new SmartFileInfo(__DIR__ . '/Source/Control/ControlFactory.php'),
new AddedFileWithContent(
'/Source/Control/ControlFactory.php',
$smartFileSystem->readFile(__DIR__ . '/Source/Control/ControlFactory.php')
),
];
}
}
?>

View File

@ -20,7 +20,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeR
class YieldStrings
{
/**
* @return string[]
* @return \Iterator<string>
*/
public function getValues(): iterable
{

View File

@ -14,7 +14,7 @@ function iterableFunction($value)
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture\IterableFunction;
function iterableFunction($value): iterable
function iterableFunction($value): \Iterator
{
yield 1;
yield 2;

View File

@ -1,16 +0,0 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
final class KeepAnonymousExtendedClass
{
public function getNodeVisitor(): NodeVisitorAbstract
{
return new class extends NodeVisitorAbstract
{
};
}
}

View File

@ -4,15 +4,6 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclaration
class KnownStaticNullable
{
public function getMoreItems()
{
if ((bool) rand(0, 100)) {
return null;
}
return [];
}
public function getStringNull()
{
/** @var string|null $value */
@ -30,15 +21,6 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclaration
class KnownStaticNullable
{
public function getMoreItems(): ?array
{
if ((bool) rand(0, 100)) {
return null;
}
return [];
}
public function getStringNull(): ?string
{
/** @var string|null $value */

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
class KnownStaticNullableArray
{
public function getMoreItems()
{
if ((bool) rand(0, 100)) {
return null;
}
return [];
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
class KnownStaticNullableArray
{
public function getMoreItems(): ?array
{
if ((bool) rand(0, 100)) {
return null;
}
return [];
}
}
?>

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use PhpParser\NodeVisitorAbstract;
final class OverrideAnonymousExtendedClass
{
public function getNodeVisitor(): NodeVisitorAbstract
{
return new class extends NodeVisitorAbstract
{
};
}
}
?>
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use PhpParser\NodeVisitorAbstract;
final class OverrideAnonymousExtendedClass
{
public function getNodeVisitor(): object
{
return new class extends NodeVisitorAbstract
{
};
}
}
?>

View File

@ -0,0 +1,19 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use Symplify\PhpConfigPrinter\HttpKernel\PhpConfigPrinterKernel;
use Symplify\PhpConfigPrinter\Printer\SmartPhpConfigPrinter;
final class SkipContainerGet
{
public function create(): SmartPhpConfigPrinter
{
$phpConfigPrinterKernel = new PhpConfigPrinterKernel('prod', true);
$phpConfigPrinterKernel->setConfigs([__DIR__ . '/config/php-config-printer-config.php']);
$phpConfigPrinterKernel->boot();
$container = $phpConfigPrinterKernel->getContainer();
return $container->get(SmartPhpConfigPrinter::class);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use Iterator;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
final class SkipIteratorAlready
{
/**
* @return Iterator<int[][]|Array_[]<string, int>[]|Array_[]>
*/
public function provideDataForArray(): Iterator
{
$array = new Array_();
$array->items[] = new ArrayItem(new LNumber(1));
yield [[1], $array];
$array = new Array_();
$array->items[] = new ArrayItem(new LNumber(1), new String_('a'));
yield [[
'a' => 1,
], $array];
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use Symfony\Component\Console\Input\StringInput;
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
final class SkipMagicCaller
{
public function run(): array
{
$privatesCaller = new PrivatesCaller();
return $privatesCaller->callPrivateMethod(new StringInput(''), 'tokenize', []);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture;
use PhpParser\Node\Scalar\String_;
use PhpParser\PrettyPrinter\Standard;
final class SkipParentStandard extends Standard
{
protected function pExpr_Cast_String(String_ $node): string
{
return parent::pExpr_Cast_String($node);
}
}

View File

@ -1,16 +1,20 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture\Inheritance;
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\FixtureInheritance;
class A {
class A
{
/** @return A */
public function test() {
public function test()
{
return $this;
}
}
class B extends A {
public function test() {
class B extends A
{
public function test()
{
return $this;
}
}
@ -19,16 +23,20 @@ class B extends A {
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture\Inheritance;
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\FixtureInheritance;
class A {
public function test(): self {
class A
{
public function test(): self
{
return $this;
}
}
class B extends A {
public function test(): \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture\Inheritance\A {
class B extends A
{
public function test(): \Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\FixtureInheritance\A
{
return $this;
}
}

View File

@ -2,7 +2,7 @@
namespace Rector\TypeDeclaration\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
final class CallableType
final class SomeCallableType
{
private $code;
/**
@ -27,10 +27,10 @@ final class CallableType
namespace Rector\TypeDeclaration\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
final class CallableType
final class SomeCallableType
{
/**
* @var null|callable
* @var callable|null
*/
private $code;
/**

View File

@ -623,7 +623,7 @@ class Command
*/
private $applicationDefinitionMergedWithArgs = false;
/**
* @var null|callable
* @var callable|null
*/
private $code;
/**

View File

@ -46,9 +46,12 @@ final class RectorConfigsResolver
}
$setFileInfos = $this->setAwareConfigResolver->resolveFromParameterSetsFromConfigFiles([$configFileInfo]);
/** @var SmartFileInfo[] $configFileInfos */
$configFileInfos = array_merge([$configFileInfo], $setFileInfos);
$this->resolvedConfigFileInfos[$hash] = $configFileInfos;
return $configFileInfos;
}

View File

@ -111,9 +111,11 @@ final class IfManipulator
if ($if->stmts === []) {
return null;
}
if (! $if->cond instanceof NotIdentical) {
return null;
}
if (! $this->isNotIdenticalNullCompare($if->cond)) {
return null;
}
@ -122,11 +124,13 @@ final class IfManipulator
if (! $insideIfNode instanceof Expression) {
return null;
}
if (! $insideIfNode->expr instanceof Assign) {
$assignedExpr = $insideIfNode->expr;
if (! $assignedExpr instanceof Assign) {
return null;
}
return $insideIfNode->expr;
return $assignedExpr;
}
/**

View File

@ -45,8 +45,13 @@ final class Parser
}
$fileContent = $this->smartFileSystem->readFile($fileRealPath);
$this->nodesByFile[$fileRealPath] = (array) $this->nikicParser->parse($fileContent);
$nodes = $this->nikicParser->parse($fileContent);
if ($nodes === null) {
$nodes = [];
}
$this->nodesByFile[$fileRealPath] = $nodes;
return $this->nodesByFile[$fileRealPath];
}
}

View File

@ -15,4 +15,9 @@ class EntityRepository
* @var EntityManager
*/
protected $_em;
public function createQueryBuilder(): QueryBuilder
{
return new QueryBuilder();
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM;
if (class_exists('Doctrine\ORM\QueryBuilder')) {
return;
}
final class QueryBuilder
{
}

View File

@ -38,6 +38,9 @@ final class NodeFactoryTest extends AbstractKernelTestCase
$this->assertEquals($expectedArrayNode, $arrayNode);
}
/**
* @return Iterator<int[][]|array<string, int>|Array_[]>
*/
public function provideDataForArray(): Iterator
{
$array = new Array_();

View File

@ -15,6 +15,7 @@ final class StaticInstanceOfTest extends TestCase
/**
* @dataProvider provideIsOneOf()
* @param class-string[] $array
* @param DateTime|stdClass|null $object
*/
public function testIsOneOf(?object $object, array $array, bool $expected): void
{

View File

@ -53,6 +53,9 @@ final class StaticRectorStringsTest extends TestCase
$this->assertSame($expected, StaticRectorStrings::underscoreToCamelCase($content));
}
/**
* @return Iterator<string[]>
*/
public function provideDataForUnderscoreToCamelCase(): Iterator
{
yield ['simple_test', 'simpleTest'];
@ -66,6 +69,9 @@ final class StaticRectorStringsTest extends TestCase
$this->assertSame($expected, StaticRectorStrings::underscoreToPascalCase($content));
}
/**
* @return Iterator<string[]>
*/
public function provideDataForUnderscoreToPascalCase(): Iterator
{
yield ['simple_test', 'SimpleTest'];