rector/rules/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRec...

209 lines
7.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare (strict_types=1);
namespace Rector\TypeDeclaration\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\UnionType as NodeUnionType;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
use Rector\Php74\Guard\MakePropertyTypedGuard;
use Rector\PhpParser\Node\Value\ValueResolver;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\Rector\AbstractRector;
use Rector\Reflection\ReflectionResolver;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\TypeDeclaration\NodeTypeAnalyzer\PropertyTypeDecorator;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\AllAssignNodePropertyTypeInferer;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector\TypedPropertyFromAssignsRectorTest
*/
final class TypedPropertyFromAssignsRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface
{
/**
* @readonly
* @var \Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\AllAssignNodePropertyTypeInferer
*/
private $allAssignNodePropertyTypeInferer;
/**
* @readonly
* @var \Rector\TypeDeclaration\NodeTypeAnalyzer\PropertyTypeDecorator
*/
private $propertyTypeDecorator;
/**
* @readonly
* @var \Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover
*/
private $varTagRemover;
/**
* @readonly
* @var \Rector\Php74\Guard\MakePropertyTypedGuard
*/
private $makePropertyTypedGuard;
/**
* @readonly
* @var \Rector\Reflection\ReflectionResolver
*/
private $reflectionResolver;
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory
*/
private $phpDocInfoFactory;
/**
* @readonly
* @var \Rector\PhpParser\Node\Value\ValueResolver
*/
private $valueResolver;
/**
* @readonly
* @var \Rector\StaticTypeMapper\StaticTypeMapper
*/
private $staticTypeMapper;
/**
* @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;
public function __construct(AllAssignNodePropertyTypeInferer $allAssignNodePropertyTypeInferer, PropertyTypeDecorator $propertyTypeDecorator, VarTagRemover $varTagRemover, MakePropertyTypedGuard $makePropertyTypedGuard, ReflectionResolver $reflectionResolver, PhpDocInfoFactory $phpDocInfoFactory, ValueResolver $valueResolver, StaticTypeMapper $staticTypeMapper)
{
$this->allAssignNodePropertyTypeInferer = $allAssignNodePropertyTypeInferer;
$this->propertyTypeDecorator = $propertyTypeDecorator;
$this->varTagRemover = $varTagRemover;
$this->makePropertyTypedGuard = $makePropertyTypedGuard;
$this->reflectionResolver = $reflectionResolver;
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->valueResolver = $valueResolver;
$this->staticTypeMapper = $staticTypeMapper;
}
public function configure(array $configuration) : void
{
$this->inlinePublic = $configuration[self::INLINE_PUBLIC] ?? (bool) \current($configuration);
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Add typed property from assigned types', [new ConfiguredCodeSample(<<<'CODE_SAMPLE'
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
, [\Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::INLINE_PUBLIC => \false])]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes() : array
{
return [Class_::class];
}
public function provideMinPhpVersion() : int
{
return PhpVersionFeature::TYPED_PROPERTIES;
}
/**
* @param Node\Stmt\Class_ $node
*/
public function refactor(Node $node) : ?Node
{
$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;
}
if (!$this->makePropertyTypedGuard->isLegal($property, $classReflection, $this->inlinePublic)) {
continue;
}
$inferredType = $this->allAssignNodePropertyTypeInferer->inferProperty($property, $classReflection, $this->file);
if (!$inferredType instanceof Type) {
continue;
}
if ($inferredType instanceof MixedType) {
continue;
}
$inferredType = $this->decorateTypeWithNullableIfDefaultPropertyNull($property, $inferredType);
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($inferredType, TypeKind::PROPERTY);
if (!$typeNode instanceof Node) {
continue;
}
$hasChanged = \true;
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
if ($inferredType instanceof UnionType && ($typeNode instanceof NodeUnionType || $typeNode instanceof NullableType)) {
$this->propertyTypeDecorator->decoratePropertyUnionType($inferredType, $typeNode, $property, $phpDocInfo, \false);
} else {
$property->type = $typeNode;
}
if (!$property->type instanceof Node) {
continue;
}
$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $property);
}
if ($hasChanged) {
return $node;
}
return null;
}
private function decorateTypeWithNullableIfDefaultPropertyNull(Property $property, Type $inferredType) : Type
{
$defaultExpr = $property->props[0]->default;
if (!$defaultExpr instanceof Expr) {
return $inferredType;
}
if (!$this->valueResolver->isNull($defaultExpr)) {
return $inferredType;
}
if (TypeCombinator::containsNull($inferredType)) {
return $inferredType;
}
return TypeCombinator::addNull($inferredType);
}
}