mirror of https://github.com/rectorphp/rector.git
209 lines
7.5 KiB
PHP
209 lines
7.5 KiB
PHP
<?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);
|
||
}
|
||
}
|