mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-08 20:22:24 +00:00
[PHP 8.1] Add IntersectionTypesRector (#1227)
This commit is contained in:
parent
62f2c30358
commit
24b8ea974a
|
@ -3,10 +3,12 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector;
|
||||
|
||||
use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector;
|
||||
use Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector;
|
||||
use Rector\Php81\Rector\ClassMethod\NewInInitializerRector;
|
||||
use Rector\Php81\Rector\FuncCall\Php81ResourceReturnToObjectRector;
|
||||
use Rector\Php81\Rector\FunctionLike\IntersectionTypesRector;
|
||||
use Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector;
|
||||
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
|
||||
|
@ -22,4 +24,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
$services->set(SpatieEnumClassToEnumRector::class);
|
||||
$services->set(Php81ResourceReturnToObjectRector::class);
|
||||
$services->set(NewInInitializerRector::class);
|
||||
$services->set(IntersectionTypesRector::class);
|
||||
};
|
||||
|
|
|
@ -5,17 +5,16 @@ declare(strict_types=1);
|
|||
namespace Rector\FamilyTree\Reflection;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\StaticPropertyFetch;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\UnionType as PhpParserUnionType;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
|
@ -68,7 +67,7 @@ final class FamilyRelationsAnalyzer
|
|||
Property $property,
|
||||
Type $varType,
|
||||
?Scope $scope,
|
||||
Name | NullableType | PhpParserUnionType | null $propertyTypeNode
|
||||
Name | ComplexType | null $propertyTypeNode
|
||||
): PropertyType {
|
||||
if ($varType instanceof UnionType) {
|
||||
return new PropertyType($varType, $propertyTypeNode);
|
||||
|
|
|
@ -4,20 +4,15 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\FamilyTree\ValueObject;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\UnionType as PhpParserUnionType;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class PropertyType
|
||||
{
|
||||
/**
|
||||
* @param Name|NullableType|PhpParserUnionType|null $propertyTypeNode
|
||||
*/
|
||||
public function __construct(
|
||||
private Type $varType,
|
||||
private ?Node $propertyTypeNode
|
||||
private Name|ComplexType|null $propertyTypeNode
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -26,10 +21,7 @@ final class PropertyType
|
|||
return $this->varType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|PhpParserUnionType|null
|
||||
*/
|
||||
public function getPropertyTypeNode(): ?Node
|
||||
public function getPropertyTypeNode(): Name|ComplexType|null
|
||||
{
|
||||
return $this->propertyTypeNode;
|
||||
}
|
||||
|
|
|
@ -5,15 +5,14 @@ declare(strict_types=1);
|
|||
namespace Rector\PHPStanStaticTypeMapper\Contract;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\UnionType;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
||||
|
||||
/**
|
||||
* @template T of Type
|
||||
* @template TType of Type
|
||||
*/
|
||||
interface TypeMapperInterface
|
||||
{
|
||||
|
@ -23,13 +22,13 @@ interface TypeMapperInterface
|
|||
public function getNodeClass(): string;
|
||||
|
||||
/**
|
||||
* @param T $type
|
||||
* @param TType $type
|
||||
*/
|
||||
public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): TypeNode;
|
||||
|
||||
/**
|
||||
* @param T $type
|
||||
* @return Name|NullableType|UnionType|null
|
||||
* @param TType $type
|
||||
* @return Name|ComplexType|null
|
||||
*/
|
||||
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): ?Node;
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\PHPStanStaticTypeMapper;
|
||||
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\UnionType;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\Accessory\AccessoryNumericStringType;
|
||||
|
@ -47,7 +46,7 @@ final class PHPStanStaticTypeMapper
|
|||
throw new NotImplementedYetException(__METHOD__ . ' for ' . $type::class);
|
||||
}
|
||||
|
||||
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): Name | NullableType | UnionType | null
|
||||
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): Name | ComplexType | null
|
||||
{
|
||||
foreach ($this->typeMappers as $typeMapper) {
|
||||
if (! is_a($type, $typeMapper->getNodeClass(), true)) {
|
||||
|
|
|
@ -5,10 +5,14 @@ declare(strict_types=1);
|
|||
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareIntersectionTypeNode;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\Php\PhpVersionProvider;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
||||
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
||||
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
|
||||
|
@ -21,6 +25,11 @@ final class IntersectionTypeMapper implements TypeMapperInterface
|
|||
{
|
||||
private PHPStanStaticTypeMapper $phpStanStaticTypeMapper;
|
||||
|
||||
public function __construct(
|
||||
private PhpVersionProvider $phpVersionProvider
|
||||
) {
|
||||
}
|
||||
|
||||
#[Required]
|
||||
public function autowireIntersectionTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
|
||||
{
|
||||
|
@ -51,6 +60,10 @@ final class IntersectionTypeMapper implements TypeMapperInterface
|
|||
|
||||
$intersectionTypesNodes = array_unique($intersectionTypesNodes);
|
||||
|
||||
if (count($intersectionTypesNodes) === 1) {
|
||||
return $intersectionTypesNodes[0];
|
||||
}
|
||||
|
||||
return new BracketsAwareIntersectionTypeNode($intersectionTypesNodes);
|
||||
}
|
||||
|
||||
|
@ -59,7 +72,21 @@ final class IntersectionTypeMapper implements TypeMapperInterface
|
|||
*/
|
||||
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): ?Node
|
||||
{
|
||||
// intersection types in PHP are not yet supported
|
||||
return null;
|
||||
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::INTERSECTION_TYPES)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$intersectionedTypeNodes = [];
|
||||
foreach ($type->getTypes() as $intersectionedType) {
|
||||
$resolvedType = $this->phpStanStaticTypeMapper->mapToPhpParserNode($intersectionedType, $typeKind);
|
||||
|
||||
if (! $resolvedType instanceof Name) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$intersectionedTypeNodes[] = $resolvedType;
|
||||
}
|
||||
|
||||
return new Node\IntersectionType($intersectionedTypeNodes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
@ -132,7 +133,7 @@ final class UnionTypeMapper implements TypeMapperInterface
|
|||
return $nullabledTypeNode;
|
||||
}
|
||||
|
||||
if ($nullabledTypeNode instanceof PhpParserUnionType) {
|
||||
if ($nullabledTypeNode instanceof ComplexType) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,12 +7,18 @@ namespace Rector\StaticTypeMapper\Contract\PhpParser;
|
|||
use PhpParser\Node;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
/**
|
||||
* @template TNode as \PhpParser\Node
|
||||
*/
|
||||
interface PhpParserNodeMapperInterface
|
||||
{
|
||||
/**
|
||||
* @return class-string<Node>
|
||||
* @return class-string<TNode>
|
||||
*/
|
||||
public function getNodeType(): string;
|
||||
|
||||
/**
|
||||
* @param TNode $node
|
||||
*/
|
||||
public function mapToPHPStan(Node $node): Type;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\StaticTypeMapper\PhpDocParser;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser\NameScope;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
|
||||
use Rector\StaticTypeMapper\PhpDoc\PhpDocTypeMapper;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
final class IntersectionTypeMapper implements PhpDocTypeMapperInterface
|
||||
{
|
||||
private PhpDocTypeMapper $phpDocTypeMapper;
|
||||
|
||||
/**
|
||||
* @return class-string<TypeNode>
|
||||
*/
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return IntersectionTypeNode::class;
|
||||
}
|
||||
|
||||
#[Required]
|
||||
public function autowireUnionTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
|
||||
{
|
||||
$this->phpDocTypeMapper = $phpDocTypeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IntersectionTypeNode $typeNode
|
||||
*/
|
||||
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
|
||||
{
|
||||
$intersectionedTypes = [];
|
||||
foreach ($typeNode->types as $intersectionedTypeNode) {
|
||||
$intersectionedTypes[] = $this->phpDocTypeMapper->mapToPHPStanType(
|
||||
$intersectionedTypeNode,
|
||||
$node,
|
||||
$nameScope
|
||||
);
|
||||
}
|
||||
|
||||
return new IntersectionType($intersectionedTypes);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@ use PHPStan\Type\Type;
|
|||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<Expr>
|
||||
*/
|
||||
final class ExprNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,9 @@ use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
|||
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
|
||||
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<FullyQualified>
|
||||
*/
|
||||
final class FullyQualifiedNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
public function __construct(
|
||||
|
|
|
@ -10,6 +10,9 @@ use PHPStan\Type\Type;
|
|||
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
||||
use Rector\StaticTypeMapper\Mapper\ScalarStringToTypeMapper;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<Identifier>
|
||||
*/
|
||||
final class IdentifierNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
public function __construct(
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\StaticTypeMapper\PhpParser;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
||||
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<Node\IntersectionType>
|
||||
*/
|
||||
final class IntersectionTypeNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
private PhpParserNodeMapper $phpParserNodeMapper;
|
||||
|
||||
#[Required]
|
||||
public function autowireUnionTypeNodeMapper(PhpParserNodeMapper $phpParserNodeMapper): void
|
||||
{
|
||||
$this->phpParserNodeMapper = $phpParserNodeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Node>
|
||||
*/
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return Node\IntersectionType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node\IntersectionType $node
|
||||
*/
|
||||
public function mapToPHPStan(Node $node): Type
|
||||
{
|
||||
$types = [];
|
||||
foreach ($node->types as $intersectionedType) {
|
||||
$types[] = $this->phpParserNodeMapper->mapToPHPStanType($intersectionedType);
|
||||
}
|
||||
|
||||
return new IntersectionType($types);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,9 @@ use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
|||
use Rector\StaticTypeMapper\ValueObject\Type\ParentObjectWithoutClassType;
|
||||
use Rector\StaticTypeMapper\ValueObject\Type\ParentStaticType;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<Name>
|
||||
*/
|
||||
final class NameNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
public function __construct(
|
||||
|
|
|
@ -13,6 +13,9 @@ use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
|||
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<NullableType>
|
||||
*/
|
||||
final class NullableTypeNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
private PhpParserNodeMapper $phpParserNodeMapper;
|
||||
|
|
|
@ -10,6 +10,9 @@ use PHPStan\Type\StringType;
|
|||
use PHPStan\Type\Type;
|
||||
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<String_>
|
||||
*/
|
||||
final class StringNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,9 @@ use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
|
|||
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
/**
|
||||
* @implements PhpParserNodeMapperInterface<UnionType>
|
||||
*/
|
||||
final class UnionTypeNodeMapper implements PhpParserNodeMapperInterface
|
||||
{
|
||||
private PhpParserNodeMapper $phpParserNodeMapper;
|
||||
|
|
|
@ -5,11 +5,10 @@ declare(strict_types=1);
|
|||
namespace Rector\StaticTypeMapper;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\UnionType as PhpParserUnionType;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
||||
|
@ -47,7 +46,7 @@ final class StaticTypeMapper
|
|||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|PhpParserUnionType|null
|
||||
* @return Name|ComplexType|null
|
||||
*/
|
||||
public function mapPHPStanTypeToPhpParserNode(Type $phpStanType, TypeKind $typeKind): ?Node
|
||||
{
|
||||
|
|
|
@ -511,3 +511,9 @@ parameters:
|
|||
-
|
||||
path: rules/Php74/Rector/Property/TypedPropertyRector.php
|
||||
message: '#Class cognitive complexity is 36, keep it under 30#'
|
||||
|
||||
-
|
||||
message: '#Autowired/inject method name must respect "autowire/inject" \+ class name#'
|
||||
paths:
|
||||
- packages/StaticTypeMapper/PhpDocParser/IntersectionTypeMapper.php
|
||||
- packages/StaticTypeMapper/PhpParser/IntersectionTypeNodeMapper.php
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\FirstTypeInterface;
|
||||
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\SecondTypeInterface;
|
||||
|
||||
final class IncludeIntersectionType
|
||||
{
|
||||
private $firstAndSecond;
|
||||
|
||||
public function __construct(FirstTypeInterface&SecondTypeInterface $firstAndSecond)
|
||||
{
|
||||
$this->firstAndSecond = $firstAndSecond;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\FirstTypeInterface;
|
||||
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\SecondTypeInterface;
|
||||
|
||||
final class IncludeIntersectionType
|
||||
{
|
||||
private \Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\FirstTypeInterface&\Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\SecondTypeInterface $firstAndSecond;
|
||||
|
||||
public function __construct(FirstTypeInterface&SecondTypeInterface $firstAndSecond)
|
||||
{
|
||||
$this->firstAndSecond = $firstAndSecond;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
class IncludeUnionedType
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ class IncludeUnionedType
|
|||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
class IncludeUnionedType
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
class SetIfElse
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ class SetIfElse
|
|||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
class SetIfElse
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
final class TwoTypes
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ final class TwoTypes
|
|||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
|
||||
|
||||
final class TwoTypes
|
||||
{
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection;
|
||||
|
||||
interface FirstTypeInterface
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection;
|
||||
|
||||
interface SecondTypeInterface
|
||||
{
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@ use Iterator;
|
|||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class UnionTypedPropertyRectorTest extends AbstractRectorTestCase
|
||||
final class UnionIntersectionTypedPropertyRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
|
@ -23,11 +23,11 @@ final class UnionTypedPropertyRectorTest extends AbstractRectorTestCase
|
|||
*/
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionTypes');
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionIntersectionTypes');
|
||||
}
|
||||
|
||||
public function provideConfigFilePath(): string
|
||||
{
|
||||
return __DIR__ . '/config/union_type_rule.php';
|
||||
return __DIR__ . '/config/union_and_intersection_type_rule.php';
|
||||
}
|
||||
}
|
|
@ -12,5 +12,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
$services->set(TypedPropertyRector::class);
|
||||
|
||||
$parameters = $containerConfigurator->parameters();
|
||||
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::UNION_TYPES);
|
||||
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::INTERSECTION_TYPES);
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector\Fixture;
|
||||
|
||||
final class SomeClass
|
||||
{
|
||||
/**
|
||||
* @param string&int $types
|
||||
*/
|
||||
public function process($types)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector\Fixture;
|
||||
|
||||
final class SomeClass
|
||||
{
|
||||
/**
|
||||
* @param string&int $types
|
||||
*/
|
||||
public function process(int&string $types)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class IntersectionTypesRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<SmartFileInfo>
|
||||
*/
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
public function provideConfigFilePath(): string
|
||||
{
|
||||
return __DIR__ . '/config/configured_rule.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Php81\Rector\FunctionLike\IntersectionTypesRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(IntersectionTypesRector::class);
|
||||
};
|
|
@ -134,7 +134,7 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
// Make it nullable?
|
||||
if ($node->returnType instanceof NullableType && ! $parentReturnTypeNode instanceof NullableType && ! $parentReturnTypeNode instanceof UnionType) {
|
||||
if ($node->returnType instanceof NullableType && ! $parentReturnTypeNode instanceof ComplexType) {
|
||||
$parentReturnTypeNode = new NullableType($parentReturnTypeNode);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,13 +5,13 @@ declare(strict_types=1);
|
|||
namespace Rector\Php74\Rector\Property;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PhpParser\Node\UnionType as PhpParserUnionType;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Type\Generic\TemplateType;
|
||||
use PHPStan\Type\MixedType;
|
||||
|
@ -165,6 +165,7 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
$scope = $node->getAttribute(AttributeKey::SCOPE);
|
||||
|
||||
$propertyType = $this->familyRelationsAnalyzer->getPossibleUnionPropertyType(
|
||||
$node,
|
||||
$varType,
|
||||
|
@ -199,7 +200,7 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
private function isNullOrNonClassLikeTypeOrMixedOrVendorLockedIn(
|
||||
Name | NullableType | PhpParserUnionType | null $node,
|
||||
Name | ComplexType | null $node,
|
||||
Property $property,
|
||||
Type $type
|
||||
): bool {
|
||||
|
@ -226,7 +227,7 @@ CODE_SAMPLE
|
|||
return true;
|
||||
}
|
||||
|
||||
private function shouldSkipNonClassLikeType(Name|NullableType|PhpParserUnionType $node, Type $type): bool
|
||||
private function shouldSkipNonClassLikeType(Name|ComplexType $node, Type $type): bool
|
||||
{
|
||||
// unwrap nullable type
|
||||
if ($node instanceof NullableType) {
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Php74\TypeAnalyzer;
|
||||
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PHPStan\Type\NullType;
|
||||
|
@ -12,7 +13,7 @@ use PHPStan\Type\UnionType;
|
|||
|
||||
final class PropertyUnionTypeResolver
|
||||
{
|
||||
public function resolve(Name|NullableType|\PhpParser\Node\UnionType $phpUnionType, Type $possibleUnionType): Type
|
||||
public function resolve(Name|ComplexType $phpUnionType, Type $possibleUnionType): Type
|
||||
{
|
||||
if (! $phpUnionType instanceof NullableType) {
|
||||
return $possibleUnionType;
|
||||
|
|
117
rules/Php81/Rector/FunctionLike/IntersectionTypesRector.php
Normal file
117
rules/Php81/Rector/FunctionLike/IntersectionTypesRector.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php81\Rector\FunctionLike;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
||||
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @see \Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector\IntersectionTypesRectorTest
|
||||
*/
|
||||
final class IntersectionTypesRector extends AbstractRector implements MinPhpVersionInterface
|
||||
{
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition(
|
||||
'Change docs to intersection types, where possible (properties are covered by TypedPropertyRector (@todo))',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
final class SomeClass
|
||||
{
|
||||
/**
|
||||
* @param string&int $types
|
||||
*/
|
||||
public function process($types)
|
||||
{
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
final class SomeClass
|
||||
{
|
||||
public function process(string&int $types)
|
||||
{
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [ArrowFunction::class, Closure::class, ClassMethod::class, Function_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrowFunction|Closure|ClassMethod|Function_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
|
||||
if (! $phpDocInfo instanceof PhpDocInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->refactorParamTypes($node, $phpDocInfo);
|
||||
// $this->refactorReturnType($node, $phpDocInfo);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function provideMinPhpVersion(): int
|
||||
{
|
||||
return PhpVersionFeature::INTERSECTION_TYPES;
|
||||
}
|
||||
|
||||
private function refactorParamTypes(
|
||||
ArrowFunction|Closure|ClassMethod|Function_ $functionLike,
|
||||
PhpDocInfo $phpDocInfo
|
||||
): void {
|
||||
foreach ($functionLike->params as $param) {
|
||||
if ($param->type !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var string $paramName */
|
||||
$paramName = $this->getName($param->var);
|
||||
$paramType = $phpDocInfo->getParamType($paramName);
|
||||
|
||||
if (! $paramType instanceof IntersectionType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phpParserIntersectionType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
|
||||
$paramType,
|
||||
TypeKind::PARAM()
|
||||
);
|
||||
|
||||
if (! $phpParserIntersectionType instanceof Node\IntersectionType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$param->type = $phpParserIntersectionType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,11 +10,11 @@ use PhpParser\Node\Expr\PropertyFetch;
|
|||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\UnionType;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\Php\PhpPropertyReflection;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\Reflection\ReflectionResolver;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
|
@ -130,7 +130,7 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
/**
|
||||
* @return Node\Name|UnionType|NullableType|null
|
||||
* @return Node\Name|Node\ComplexType|null
|
||||
*/
|
||||
private function matchPropertySingleTypeNode(PropertyFetch $propertyFetch): ?Node
|
||||
{
|
||||
|
@ -145,7 +145,7 @@ CODE_SAMPLE
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($propertyType instanceof \PHPStan\Type\UnionType) {
|
||||
if ($propertyType instanceof UnionType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -536,4 +536,10 @@ final class PhpVersionFeature
|
|||
* @var int
|
||||
*/
|
||||
public const NEW_INITIALIZERS = PhpVersion::PHP_81;
|
||||
|
||||
/**
|
||||
* @see https://wiki.php.net/rfc/pure-intersection-types
|
||||
* @var int
|
||||
*/
|
||||
public const INTERSECTION_TYPES = PhpVersion::PHP_81;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user