-
-
Notifications
You must be signed in to change notification settings - Fork 671
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Updated Rector to commit a8ae2d8c2a92536f95a70dc99eaebb01168c4a6f
rectorphp/rector-src@a8ae2d8 implemented TypedPropertyFromStrictConstructorReadonlyClassRector (#4552)
- Loading branch information
1 parent
f8344bb
commit 1191015
Showing
10 changed files
with
270 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
...TypeDeclaration/Rector/Property/TypedPropertyFromStrictConstructorReadonlyClassRector.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
<?php | ||
|
||
declare (strict_types=1); | ||
namespace Rector\TypeDeclaration\Rector\Property; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr; | ||
use PhpParser\Node\Stmt\Class_; | ||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PhpParser\Node\Stmt\Property; | ||
use PhpParser\Node\Stmt\PropertyProperty; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\ClassReflection; | ||
use PHPStan\Reflection\Php\PhpPropertyReflection; | ||
use PHPStan\Type\MixedType; | ||
use PHPStan\Type\ObjectType; | ||
use PHPStan\Type\Type; | ||
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; | ||
use Rector\Core\Rector\AbstractRector; | ||
use Rector\Core\Rector\AbstractScopeAwareRector; | ||
use Rector\Core\Reflection\ReflectionResolver; | ||
use Rector\Core\ValueObject\MethodName; | ||
use Rector\Core\ValueObject\PhpVersionFeature; | ||
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover; | ||
use Rector\PHPStanStaticTypeMapper\DoctrineTypeAnalyzer; | ||
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; | ||
use Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector; | ||
use Rector\TypeDeclaration\Guard\PropertyTypeOverrideGuard; | ||
use Rector\TypeDeclaration\TypeAnalyzer\PropertyTypeDefaultValueAnalyzer; | ||
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\TrustedClassMethodPropertyTypeInferer; | ||
use Rector\VersionBonding\Contract\MinPhpVersionInterface; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
/** | ||
* @see \Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorReadonlyClassRector\TypedPropertyFromStrictConstructorReadonlyClassRectorTest | ||
*/ | ||
final class TypedPropertyFromStrictConstructorReadonlyClassRector extends AbstractScopeAwareRector implements MinPhpVersionInterface | ||
{ | ||
/** | ||
* @readonly | ||
* @var \Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\TrustedClassMethodPropertyTypeInferer | ||
*/ | ||
private $trustedClassMethodPropertyTypeInferer; | ||
/** | ||
* @readonly | ||
* @var \Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover | ||
*/ | ||
private $varTagRemover; | ||
/** | ||
* @readonly | ||
* @var \Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector | ||
*/ | ||
private $constructorAssignDetector; | ||
/** | ||
* @readonly | ||
* @var \Rector\TypeDeclaration\Guard\PropertyTypeOverrideGuard | ||
*/ | ||
private $propertyTypeOverrideGuard; | ||
/** | ||
* @readonly | ||
* @var \Rector\Core\Reflection\ReflectionResolver | ||
*/ | ||
private $reflectionResolver; | ||
/** | ||
* @readonly | ||
* @var \Rector\PHPStanStaticTypeMapper\DoctrineTypeAnalyzer | ||
*/ | ||
private $doctrineTypeAnalyzer; | ||
/** | ||
* @readonly | ||
* @var \Rector\TypeDeclaration\TypeAnalyzer\PropertyTypeDefaultValueAnalyzer | ||
*/ | ||
private $propertyTypeDefaultValueAnalyzer; | ||
public function __construct(TrustedClassMethodPropertyTypeInferer $trustedClassMethodPropertyTypeInferer, VarTagRemover $varTagRemover, ConstructorAssignDetector $constructorAssignDetector, PropertyTypeOverrideGuard $propertyTypeOverrideGuard, ReflectionResolver $reflectionResolver, DoctrineTypeAnalyzer $doctrineTypeAnalyzer, PropertyTypeDefaultValueAnalyzer $propertyTypeDefaultValueAnalyzer) | ||
{ | ||
$this->trustedClassMethodPropertyTypeInferer = $trustedClassMethodPropertyTypeInferer; | ||
$this->varTagRemover = $varTagRemover; | ||
$this->constructorAssignDetector = $constructorAssignDetector; | ||
$this->propertyTypeOverrideGuard = $propertyTypeOverrideGuard; | ||
$this->reflectionResolver = $reflectionResolver; | ||
$this->doctrineTypeAnalyzer = $doctrineTypeAnalyzer; | ||
$this->propertyTypeDefaultValueAnalyzer = $propertyTypeDefaultValueAnalyzer; | ||
} | ||
public function getRuleDefinition() : RuleDefinition | ||
{ | ||
return new RuleDefinition('Add typed public properties based only on strict constructor types in readonly classes', [new CodeSample(<<<'CODE_SAMPLE' | ||
/** | ||
* @immutable | ||
*/ | ||
class SomeObject | ||
{ | ||
public $name; | ||
public function __construct(string $name) | ||
{ | ||
$this->name = $name; | ||
} | ||
} | ||
CODE_SAMPLE | ||
, <<<'CODE_SAMPLE' | ||
/** | ||
* @immutable | ||
*/ | ||
class SomeObject | ||
{ | ||
public string $name; | ||
public function __construct(string $name) | ||
{ | ||
$this->name = $name; | ||
} | ||
} | ||
CODE_SAMPLE | ||
)]); | ||
} | ||
/** | ||
* @return array<class-string<Node>> | ||
*/ | ||
public function getNodeTypes() : array | ||
{ | ||
return [Class_::class]; | ||
} | ||
/** | ||
* @param Class_ $node | ||
*/ | ||
public function refactorWithScope(Node $node, Scope $scope) : ?Node | ||
{ | ||
$constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); | ||
if (!$constructClassMethod instanceof ClassMethod || $node->getProperties() === []) { | ||
return null; | ||
} | ||
$classReflection = $this->reflectionResolver->resolveClassReflection($node); | ||
if (!$classReflection instanceof ClassReflection) { | ||
return null; | ||
} | ||
$hasChanged = \false; | ||
foreach ($node->getProperties() as $property) { | ||
if (!$this->propertyTypeOverrideGuard->isLegal($property, $classReflection)) { | ||
continue; | ||
} | ||
$propertyType = $this->trustedClassMethodPropertyTypeInferer->inferProperty($node, $property, $constructClassMethod); | ||
if ($this->shouldSkipProperty($property, $propertyType, $classReflection, $scope)) { | ||
continue; | ||
} | ||
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); | ||
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType, TypeKind::PROPERTY); | ||
if (!$propertyTypeNode instanceof Node) { | ||
continue; | ||
} | ||
$propertyProperty = $property->props[0]; | ||
$propertyName = $this->nodeNameResolver->getName($property); | ||
if ($this->constructorAssignDetector->isPropertyAssigned($node, $propertyName)) { | ||
$propertyProperty->default = null; | ||
$hasChanged = \true; | ||
} | ||
if ($this->propertyTypeDefaultValueAnalyzer->doesConflictWithDefaultValue($propertyProperty, $propertyType)) { | ||
continue; | ||
} | ||
$property->type = $propertyTypeNode; | ||
$this->varTagRemover->removeVarTagIfUseless($phpDocInfo, $property); | ||
$hasChanged = \true; | ||
} | ||
if ($hasChanged) { | ||
return $node; | ||
} | ||
return null; | ||
} | ||
public function provideMinPhpVersion() : int | ||
{ | ||
return PhpVersionFeature::TYPED_PROPERTIES; | ||
} | ||
private function shouldSkipProperty(Property $property, Type $propertyType, ClassReflection $classReflection, Scope $scope) : bool | ||
{ | ||
if (!$property->isPublic()) { | ||
return \true; | ||
} | ||
if ($propertyType instanceof MixedType) { | ||
return \true; | ||
} | ||
if ($this->doctrineTypeAnalyzer->isInstanceOfCollectionType($propertyType)) { | ||
return \true; | ||
} | ||
$isReadOnlyByPhpdoc = \false; | ||
$propertyName = $this->nodeNameResolver->getName($property); | ||
if ($classReflection->hasProperty($propertyName)) { | ||
$propertyReflection = $classReflection->getProperty($propertyName, $scope); | ||
if ($propertyReflection instanceof PhpPropertyReflection) { | ||
$isReadOnlyByPhpdoc = $propertyReflection->isReadOnlyByPhpDoc(); | ||
} | ||
} | ||
if (!$isReadOnlyByPhpdoc) { | ||
return \true; | ||
} | ||
return \false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
rules/TypeDeclaration/TypeAnalyzer/PropertyTypeDefaultValueAnalyzer.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare (strict_types=1); | ||
namespace Rector\TypeDeclaration\TypeAnalyzer; | ||
|
||
use PhpParser\Node\Expr; | ||
use PhpParser\Node\Stmt\PropertyProperty; | ||
use PHPStan\Type\Type; | ||
use Rector\StaticTypeMapper\StaticTypeMapper; | ||
final class PropertyTypeDefaultValueAnalyzer | ||
{ | ||
/** | ||
* @readonly | ||
* @var \Rector\StaticTypeMapper\StaticTypeMapper | ||
*/ | ||
private $staticTypeMapper; | ||
public function __construct(StaticTypeMapper $staticTypeMapper) | ||
{ | ||
$this->staticTypeMapper = $staticTypeMapper; | ||
} | ||
public function doesConflictWithDefaultValue(PropertyProperty $propertyProperty, Type $propertyType) : bool | ||
{ | ||
if (!$propertyProperty->default instanceof Expr) { | ||
return \false; | ||
} | ||
// the defaults can be in conflict | ||
$defaultType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($propertyProperty->default); | ||
// type is not matching, skip it | ||
return !$defaultType->isSuperTypeOf($propertyType)->yes(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.