Skip to content

Commit

Permalink
[TypeDeclaration] Make TypedPropertyFromStrictSetUpRector use Class_ (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Nov 29, 2022
1 parent e8e3bd5 commit e28b04a
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 91 deletions.
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -791,3 +791,4 @@ parameters:
# on purpose, as rule it about to be removed
- '#Class "Rector\\TypeDeclaration\\Rector\\ClassMethod\\AddArrayParamDocTypeRector" is missing @see annotation with test case class reference#'
- '#Register "Rector\\Php74\\Rector\\Property\\TypedPropertyRector" service to "php74\.php" config set#'
- '#Cognitive complexity for "Rector\\TypeDeclaration\\Rector\\Property\\TypedPropertyFromStrictConstructorRector\:\:refactor\(\)" is 11, keep it under 10#'
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\Rector\AbstractRector;
Expand Down Expand Up @@ -77,63 +77,74 @@ public function __construct(string $name)
*/
public function getNodeTypes(): array
{
return [Property::class];
return [Class_::class];
}

/**
* @param Property $node
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkip($node)) {
return null;
}
$hasChanged = false;

$propertyType = $this->trustedClassMethodPropertyTypeInferer->inferProperty($node, MethodName::CONSTRUCT);
if (! $propertyType instanceof Type) {
$constructClassMethod = $node->getMethod(MethodName::CONSTRUCT);
if (! $constructClassMethod instanceof ClassMethod) {
return null;
}

if ($propertyType instanceof MixedType) {
return null;
}
foreach ($node->getProperties() as $property) {
if ($this->shouldSkipProperty($property, $node)) {
continue;
}

$classLike = $this->betterNodeFinder->findParentType($node, Class_::class);
if (! $classLike instanceof Class_) {
return null;
}
$propertyType = $this->trustedClassMethodPropertyTypeInferer->inferProperty(
$property,
$constructClassMethod
);

if (! $this->propertyTypeOverrideGuard->isLegal($node)) {
return null;
}
if ($propertyType instanceof MixedType) {
continue;
}

$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES)) {
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $propertyType);
return $node;
}
if (! $this->propertyTypeOverrideGuard->isLegal($property)) {
continue;
}

$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType, TypeKind::PROPERTY);
if (! $propertyTypeNode instanceof Node) {
return null;
}
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);

// public property can be anything
if ($node->isPublic()) {
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $propertyType);
return $node;
}
// public property can be anything
if ($this->isVarDocPreffered($property)) {
$this->phpDocTypeChanger->changeVarType($phpDocInfo, $propertyType);
$hasChanged = true;
continue;
}

$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$propertyType,
TypeKind::PROPERTY
);
if (! $propertyTypeNode instanceof Node) {
continue;
}

$node->type = $propertyTypeNode;
$propertyName = $this->nodeNameResolver->getName($node);
if (! $property->isPublic()) {
$property->type = $propertyTypeNode;
}

$propertyName = $this->nodeNameResolver->getName($property);

if ($this->constructorAssignDetector->isPropertyAssigned($node, $propertyName)) {
$property->props[0]->default = null;
}

if ($this->constructorAssignDetector->isPropertyAssigned($classLike, $propertyName)) {
$node->props[0]->default = null;
$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $property);
}

$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $node);
if ($hasChanged) {
return $node;
}

return $node;
return null;
}

public function provideMinPhpVersion(): int
Expand Down Expand Up @@ -166,20 +177,22 @@ private function resolveTraitPropertyNames(Class_ $class): array
return $traitPropertyNames;
}

private function shouldSkip(Property $property): bool
private function shouldSkipProperty(Property $property, Class_ $class): bool
{
if ($property->type !== null) {
return true;
}

$class = $this->betterNodeFinder->findParentType($property, Class_::class);
if ($class instanceof Class_) {
$traitPropertyNames = $this->resolveTraitPropertyNames($class);
if ($this->isNames($property, $traitPropertyNames)) {
return true;
}
$traitPropertyNames = $this->resolveTraitPropertyNames($class);
return $this->isNames($property, $traitPropertyNames);
}

private function isVarDocPreffered(Property $property): bool
{
if ($property->isPublic()) {
return true;
}

return false;
return ! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::TYPED_PROPERTIES);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\Type;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\MethodName;
use Rector\Core\ValueObject\PhpVersionFeature;
Expand Down Expand Up @@ -68,43 +67,51 @@ public function setUp()
*/
public function getNodeTypes(): array
{
return [Property::class];
return [Class_::class];
}

/**
* @param Property $node
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
// type is already set
if ($node->type !== null) {
$setUpClassMethod = $node->getMethod(MethodName::SET_UP);
if (! $setUpClassMethod instanceof ClassMethod) {
return null;
}

// is not private? we cannot be sure about other usage
if (! $node->isPrivate()) {
return null;
}
$hasChanged = false;

$propertyType = $this->trustedClassMethodPropertyTypeInferer->inferProperty($node, MethodName::SET_UP);
if (! $propertyType instanceof Type) {
return null;
}
foreach ($node->getProperties() as $property) {
// type is already set
if ($property->type !== null) {
continue;
}

// skip unless a class type; we cannot be sure about traits
$class = $this->betterNodeFinder->findParentType($node, Class_::class);
if (! $class instanceof Class_) {
return null;
}
// is not private? we cannot be sure about other usage
if (! $property->isPrivate()) {
continue;
}

$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType, TypeKind::PROPERTY);
if (! $propertyTypeNode instanceof Node) {
return null;
$propertyType = $this->trustedClassMethodPropertyTypeInferer->inferProperty($property, $setUpClassMethod);

$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$propertyType,
TypeKind::PROPERTY
);
if (! $propertyTypeNode instanceof Node) {
continue;
}

$property->type = $propertyTypeNode;
$hasChanged = true;
}

$node->type = $propertyTypeNode;
if ($hasChanged) {
return $node;
}

return $node;
return null;
}

public function provideMinPhpVersion(): int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,19 @@ public function __construct(
) {
}

public function inferProperty(Property $property, string $methodName): ?Type
public function inferProperty(Property $property, ClassMethod $classMethod): Type
{
$classLike = $this->betterNodeFinder->findParentType($property, ClassLike::class);
if (! $classLike instanceof ClassLike) {
return null;
}

$classMethod = $classLike->getMethod($methodName);
if (! $classMethod instanceof ClassMethod) {
return null;
return new MixedType();
}

$propertyName = $this->nodeNameResolver->getName($property);

// 1. direct property = param assign
$param = $this->classMethodPropertyFetchManipulator->findParamAssignToPropertyName($classMethod, $propertyName);
if ($param instanceof Param) {
if ($param->type === null) {
return null;
}

$resolvedType = $this->resolveFromParamType($param, $classMethod, $propertyName);
return $this->resolveType($property, $propertyName, $classLike, $resolvedType);
return $this->resolveTypeFromParam($param, $classMethod, $propertyName, $property, $classLike);
}

// 2. different assign
Expand All @@ -95,7 +85,7 @@ public function inferProperty(Property $property, string $methodName): ?Type
}

if ($resolvedTypes === []) {
return null;
return new MixedType();
}

$resolvedType = count($resolvedTypes) === 1
Expand All @@ -109,12 +99,8 @@ private function resolveType(
Property $property,
string $propertyName,
ClassLike $classLike,
?Type $resolvedType
): ?Type {
if (! $resolvedType instanceof Type) {
return null;
}

Type $resolvedType
): Type {
$exactType = $this->assignToPropertyTypeInferer->inferPropertyInClassLike(
$property,
$propertyName,
Expand All @@ -129,7 +115,7 @@ private function resolveType(
return $resolvedType;
}

return null;
return new MixedType();
}

private function resolveFromParamType(Param $param, ClassMethod $classMethod, string $propertyName): Type
Expand Down Expand Up @@ -253,4 +239,19 @@ private function resolveFullyQualifiedOrAliasedObjectType(Param $param): ?Type

return null;
}

private function resolveTypeFromParam(
Param $param,
ClassMethod $classMethod,
string $propertyName,
Property $property,
ClassLike $classLike
): Type {
if ($param->type === null) {
return new MixedType();
}

$resolvedType = $this->resolveFromParamType($param, $classMethod, $propertyName);
return $this->resolveType($property, $propertyName, $classLike, $resolvedType);
}
}

0 comments on commit e28b04a

Please sign in to comment.