2021-12-14 21:02:15 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
|
namespace Rector\TypeDeclaration\Rector\Property;
|
2021-12-14 21:02:15 +00:00
|
|
|
|
|
2022-06-06 17:12:56 +00:00
|
|
|
|
use PhpParser\Node;
|
|
|
|
|
use PhpParser\Node\Expr;
|
2024-05-08 13:00:27 +00:00
|
|
|
|
use PhpParser\Node\NullableType;
|
2023-07-19 10:37:33 +00:00
|
|
|
|
use PhpParser\Node\Stmt\Class_;
|
2022-06-06 17:12:56 +00:00
|
|
|
|
use PhpParser\Node\Stmt\Property;
|
2024-05-08 13:00:27 +00:00
|
|
|
|
use PhpParser\Node\UnionType as NodeUnionType;
|
2023-07-19 10:37:33 +00:00
|
|
|
|
use PHPStan\Reflection\ClassReflection;
|
2022-06-06 17:12:56 +00:00
|
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
|
use PHPStan\Type\Type;
|
|
|
|
|
use PHPStan\Type\TypeCombinator;
|
|
|
|
|
use PHPStan\Type\UnionType;
|
2023-09-20 12:41:26 +00:00
|
|
|
|
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
|
2024-01-02 02:40:38 +00:00
|
|
|
|
use Rector\Contract\Rector\ConfigurableRectorInterface;
|
2022-06-06 17:12:56 +00:00
|
|
|
|
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
|
|
|
|
|
use Rector\Php74\Guard\MakePropertyTypedGuard;
|
2024-01-02 02:40:38 +00:00
|
|
|
|
use Rector\PhpParser\Node\Value\ValueResolver;
|
2022-06-06 17:12:56 +00:00
|
|
|
|
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
2024-01-02 02:40:38 +00:00
|
|
|
|
use Rector\Rector\AbstractRector;
|
|
|
|
|
use Rector\Reflection\ReflectionResolver;
|
2023-09-29 12:48:57 +00:00
|
|
|
|
use Rector\StaticTypeMapper\StaticTypeMapper;
|
2022-06-06 17:12:56 +00:00
|
|
|
|
use Rector\TypeDeclaration\NodeTypeAnalyzer\PropertyTypeDecorator;
|
|
|
|
|
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\AllAssignNodePropertyTypeInferer;
|
2024-01-02 02:40:38 +00:00
|
|
|
|
use Rector\ValueObject\PhpVersionFeature;
|
2023-05-07 08:00:09 +00:00
|
|
|
|
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
|
2022-06-07 09:18:30 +00:00
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
|
|
|
|
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
2021-12-14 21:02:15 +00:00
|
|
|
|
/**
|
|
|
|
|
* @see \Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector\TypedPropertyFromAssignsRectorTest
|
|
|
|
|
*/
|
2023-07-19 11:08:33 +00:00
|
|
|
|
final class TypedPropertyFromAssignsRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface
|
2021-12-14 21:02:15 +00:00
|
|
|
|
{
|
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
|
* @readonly
|
2021-12-14 21:02:15 +00:00
|
|
|
|
* @var \Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\AllAssignNodePropertyTypeInferer
|
|
|
|
|
*/
|
|
|
|
|
private $allAssignNodePropertyTypeInferer;
|
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
|
* @readonly
|
2021-12-14 21:02:15 +00:00
|
|
|
|
* @var \Rector\TypeDeclaration\NodeTypeAnalyzer\PropertyTypeDecorator
|
|
|
|
|
*/
|
|
|
|
|
private $propertyTypeDecorator;
|
2021-12-15 14:50:00 +00:00
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
|
* @readonly
|
2021-12-15 14:50:00 +00:00
|
|
|
|
* @var \Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover
|
|
|
|
|
*/
|
|
|
|
|
private $varTagRemover;
|
2022-04-09 07:29:28 +00:00
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
|
* @readonly
|
2022-04-09 07:29:28 +00:00
|
|
|
|
* @var \Rector\Php74\Guard\MakePropertyTypedGuard
|
|
|
|
|
*/
|
|
|
|
|
private $makePropertyTypedGuard;
|
2023-07-19 10:37:33 +00:00
|
|
|
|
/**
|
|
|
|
|
* @readonly
|
2024-01-02 02:40:38 +00:00
|
|
|
|
* @var \Rector\Reflection\ReflectionResolver
|
2023-07-19 10:37:33 +00:00
|
|
|
|
*/
|
|
|
|
|
private $reflectionResolver;
|
2023-09-20 12:41:26 +00:00
|
|
|
|
/**
|
|
|
|
|
* @readonly
|
|
|
|
|
* @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory
|
|
|
|
|
*/
|
|
|
|
|
private $phpDocInfoFactory;
|
2023-09-20 12:53:23 +00:00
|
|
|
|
/**
|
|
|
|
|
* @readonly
|
2024-01-02 02:40:38 +00:00
|
|
|
|
* @var \Rector\PhpParser\Node\Value\ValueResolver
|
2023-09-20 12:53:23 +00:00
|
|
|
|
*/
|
|
|
|
|
private $valueResolver;
|
2023-09-29 12:48:57 +00:00
|
|
|
|
/**
|
|
|
|
|
* @readonly
|
|
|
|
|
* @var \Rector\StaticTypeMapper\StaticTypeMapper
|
|
|
|
|
*/
|
|
|
|
|
private $staticTypeMapper;
|
2023-06-08 22:00:17 +00:00
|
|
|
|
/**
|
|
|
|
|
* @api
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
|
|
|
|
public const INLINE_PUBLIC = 'inline_public';
|
|
|
|
|
/**
|
|
|
|
|
* Default to false, which only apply changes:
|
|
|
|
|
*
|
|
|
|
|
* – private modifier property
|
|
|
|
|
* - protected modifier property on final class without extends or has extends but property and/or its usage only in current class
|
|
|
|
|
*
|
|
|
|
|
* Set to true will allow change other modifiers as well as far as not forbidden, eg: callable type, null type, etc.
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
private $inlinePublic = \false;
|
2023-09-29 12:48:57 +00:00
|
|
|
|
public function __construct(AllAssignNodePropertyTypeInferer $allAssignNodePropertyTypeInferer, PropertyTypeDecorator $propertyTypeDecorator, VarTagRemover $varTagRemover, MakePropertyTypedGuard $makePropertyTypedGuard, ReflectionResolver $reflectionResolver, PhpDocInfoFactory $phpDocInfoFactory, ValueResolver $valueResolver, StaticTypeMapper $staticTypeMapper)
|
2021-12-14 21:02:15 +00:00
|
|
|
|
{
|
|
|
|
|
$this->allAssignNodePropertyTypeInferer = $allAssignNodePropertyTypeInferer;
|
|
|
|
|
$this->propertyTypeDecorator = $propertyTypeDecorator;
|
2021-12-15 14:50:00 +00:00
|
|
|
|
$this->varTagRemover = $varTagRemover;
|
2022-04-09 07:29:28 +00:00
|
|
|
|
$this->makePropertyTypedGuard = $makePropertyTypedGuard;
|
2023-07-19 10:37:33 +00:00
|
|
|
|
$this->reflectionResolver = $reflectionResolver;
|
2023-09-20 12:41:26 +00:00
|
|
|
|
$this->phpDocInfoFactory = $phpDocInfoFactory;
|
2023-09-20 12:53:23 +00:00
|
|
|
|
$this->valueResolver = $valueResolver;
|
2023-09-29 12:48:57 +00:00
|
|
|
|
$this->staticTypeMapper = $staticTypeMapper;
|
2021-12-14 21:02:15 +00:00
|
|
|
|
}
|
2022-04-13 16:35:59 +00:00
|
|
|
|
public function configure(array $configuration) : void
|
|
|
|
|
{
|
|
|
|
|
$this->inlinePublic = $configuration[self::INLINE_PUBLIC] ?? (bool) \current($configuration);
|
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
|
public function getRuleDefinition() : RuleDefinition
|
2021-12-14 21:02:15 +00:00
|
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
|
return new RuleDefinition('Add typed property from assigned types', [new ConfiguredCodeSample(<<<'CODE_SAMPLE'
|
2021-12-14 21:02:15 +00:00
|
|
|
|
final class SomeClass
|
|
|
|
|
{
|
|
|
|
|
private $name;
|
|
|
|
|
|
|
|
|
|
public function run()
|
|
|
|
|
{
|
|
|
|
|
$this->name = 'string';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CODE_SAMPLE
|
|
|
|
|
, <<<'CODE_SAMPLE'
|
|
|
|
|
final class SomeClass
|
|
|
|
|
{
|
|
|
|
|
private string|null $name = null;
|
|
|
|
|
|
|
|
|
|
public function run()
|
|
|
|
|
{
|
|
|
|
|
$this->name = 'string';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CODE_SAMPLE
|
2023-12-16 15:42:57 +00:00
|
|
|
|
, [\Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::INLINE_PUBLIC => \false])]);
|
2021-12-14 21:02:15 +00:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* @return array<class-string<Node>>
|
|
|
|
|
*/
|
|
|
|
|
public function getNodeTypes() : array
|
|
|
|
|
{
|
2023-07-19 10:37:33 +00:00
|
|
|
|
return [Class_::class];
|
2021-12-14 21:02:15 +00:00
|
|
|
|
}
|
2023-05-07 08:00:09 +00:00
|
|
|
|
public function provideMinPhpVersion() : int
|
|
|
|
|
{
|
|
|
|
|
return PhpVersionFeature::TYPED_PROPERTIES;
|
|
|
|
|
}
|
2021-12-14 21:02:15 +00:00
|
|
|
|
/**
|
2023-07-19 10:37:33 +00:00
|
|
|
|
* @param Node\Stmt\Class_ $node
|
2021-12-14 21:02:15 +00:00
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
|
public function refactor(Node $node) : ?Node
|
2021-12-14 21:02:15 +00:00
|
|
|
|
{
|
2023-07-19 10:37:33 +00:00
|
|
|
|
$hasChanged = \false;
|
|
|
|
|
$classReflection = null;
|
|
|
|
|
foreach ($node->getProperties() as $property) {
|
|
|
|
|
// non-private property can be anything with not inline public configured
|
|
|
|
|
if (!$property->isPrivate() && !$this->inlinePublic) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!$classReflection instanceof ClassReflection) {
|
|
|
|
|
$classReflection = $this->reflectionResolver->resolveClassReflection($node);
|
|
|
|
|
}
|
|
|
|
|
if (!$classReflection instanceof ClassReflection) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2023-07-20 07:24:15 +00:00
|
|
|
|
if (!$this->makePropertyTypedGuard->isLegal($property, $classReflection, $this->inlinePublic)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-12-24 03:11:42 +00:00
|
|
|
|
$inferredType = $this->allAssignNodePropertyTypeInferer->inferProperty($property, $classReflection, $this->file);
|
2023-07-19 10:37:33 +00:00
|
|
|
|
if (!$inferredType instanceof Type) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ($inferredType instanceof MixedType) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$inferredType = $this->decorateTypeWithNullableIfDefaultPropertyNull($property, $inferredType);
|
|
|
|
|
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($inferredType, TypeKind::PROPERTY);
|
2023-09-17 00:30:04 +00:00
|
|
|
|
if (!$typeNode instanceof Node) {
|
2023-07-19 10:37:33 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$hasChanged = \true;
|
|
|
|
|
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
|
2024-05-08 13:00:27 +00:00
|
|
|
|
if ($inferredType instanceof UnionType && ($typeNode instanceof NodeUnionType || $typeNode instanceof NullableType)) {
|
2023-07-19 10:37:33 +00:00
|
|
|
|
$this->propertyTypeDecorator->decoratePropertyUnionType($inferredType, $typeNode, $property, $phpDocInfo, \false);
|
|
|
|
|
} else {
|
|
|
|
|
$property->type = $typeNode;
|
|
|
|
|
}
|
|
|
|
|
if (!$property->type instanceof Node) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $property);
|
2021-12-14 21:02:15 +00:00
|
|
|
|
}
|
2023-07-19 10:37:33 +00:00
|
|
|
|
if ($hasChanged) {
|
|
|
|
|
return $node;
|
2022-12-06 16:54:58 +00:00
|
|
|
|
}
|
2023-07-19 10:37:33 +00:00
|
|
|
|
return null;
|
2021-12-14 21:02:15 +00:00
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
|
private function decorateTypeWithNullableIfDefaultPropertyNull(Property $property, Type $inferredType) : Type
|
2021-12-15 14:50:00 +00:00
|
|
|
|
{
|
|
|
|
|
$defaultExpr = $property->props[0]->default;
|
2022-06-07 08:22:29 +00:00
|
|
|
|
if (!$defaultExpr instanceof Expr) {
|
2021-12-15 14:50:00 +00:00
|
|
|
|
return $inferredType;
|
|
|
|
|
}
|
|
|
|
|
if (!$this->valueResolver->isNull($defaultExpr)) {
|
|
|
|
|
return $inferredType;
|
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
|
if (TypeCombinator::containsNull($inferredType)) {
|
2021-12-15 14:50:00 +00:00
|
|
|
|
return $inferredType;
|
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
|
return TypeCombinator::addNull($inferredType);
|
2021-12-15 14:50:00 +00:00
|
|
|
|
}
|
2021-12-14 21:02:15 +00:00
|
|
|
|
}
|