Skip to content

Commit

Permalink
Updated Rector to commit d7bfc24bedc11374868f5a7ff3066eb847e80a8c
Browse files Browse the repository at this point in the history
rectorphp/rector-src@d7bfc24 [CodeQuality] Add DynamicDocBlockPropertyToNativePropertyRector (#5691)
  • Loading branch information
TomasVotruba committed Oct 12, 2024
1 parent b862c5c commit 8defa24
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 2 deletions.
46 changes: 46 additions & 0 deletions rules/CodeQuality/NodeFactory/TypedPropertyFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare (strict_types=1);
namespace Rector\CodeQuality\NodeFactory;

use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\StaticTypeMapper\StaticTypeMapper;
final class TypedPropertyFactory
{
/**
* @readonly
* @var \Rector\StaticTypeMapper\StaticTypeMapper
*/
private $staticTypeMapper;
public function __construct(StaticTypeMapper $staticTypeMapper)
{
$this->staticTypeMapper = $staticTypeMapper;
}
public function createFromPropertyTagValueNode(PropertyTagValueNode $propertyTagValueNode, Class_ $class, string $propertyName) : Property
{
$propertyProperty = new PropertyProperty($propertyName);
$propertyTypeNode = $this->createPropertyTypeNode($propertyTagValueNode, $class);
return new Property(Class_::MODIFIER_PRIVATE, [$propertyProperty], [], $propertyTypeNode);
}
/**
* @return \PhpParser\Node\Name|\PhpParser\Node\ComplexType|\PhpParser\Node\Identifier|null
*/
public function createPropertyTypeNode(PropertyTagValueNode $propertyTagValueNode, Class_ $class, bool $isNullable = \true)
{
$propertyType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($propertyTagValueNode->type, $class);
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType, TypeKind::PROPERTY);
if ($isNullable && !$typeNode instanceof NullableType && !$typeNode instanceof ComplexType && $typeNode instanceof Node) {
return new NullableType($typeNode);
}
return $typeNode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?php

declare (strict_types=1);
namespace Rector\CodeQuality\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover;
use Rector\CodeQuality\NodeFactory\TypedPropertyFactory;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
use Rector\PhpParser\Node\Value\ValueResolver;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\MethodName;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\CodeQuality\Rector\Class_\DynamicDocBlockPropertyToNativePropertyRector\DynamicDocBlockPropertyToNativePropertyRectorTest
*/
final class DynamicDocBlockPropertyToNativePropertyRector extends AbstractRector implements MinPhpVersionInterface
{
/**
* @readonly
* @var \Rector\Doctrine\NodeAnalyzer\AttributeFinder
*/
private $attributeFinder;
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory
*/
private $phpDocInfoFactory;
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover
*/
private $phpDocTagRemover;
/**
* @readonly
* @var \Rector\Comments\NodeDocBlock\DocBlockUpdater
*/
private $docBlockUpdater;
/**
* @readonly
* @var \Rector\CodeQuality\NodeFactory\TypedPropertyFactory
*/
private $typedPropertyFactory;
/**
* @readonly
* @var \Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer
*/
private $testsNodeAnalyzer;
/**
* @readonly
* @var \Rector\PhpParser\Node\Value\ValueResolver
*/
private $valueResolver;
public function __construct(AttributeFinder $attributeFinder, PhpDocInfoFactory $phpDocInfoFactory, PhpDocTagRemover $phpDocTagRemover, DocBlockUpdater $docBlockUpdater, TypedPropertyFactory $typedPropertyFactory, TestsNodeAnalyzer $testsNodeAnalyzer, ValueResolver $valueResolver)
{
$this->attributeFinder = $attributeFinder;
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->phpDocTagRemover = $phpDocTagRemover;
$this->docBlockUpdater = $docBlockUpdater;
$this->typedPropertyFactory = $typedPropertyFactory;
$this->testsNodeAnalyzer = $testsNodeAnalyzer;
$this->valueResolver = $valueResolver;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Turn dynamic docblock properties on class with no parents to explicit ones', [new CodeSample(<<<'CODE_SAMPLE'
/**
* @property SomeDependency $someDependency
*/
#[\AllowDynamicProperties]
final class SomeClass
{
public function __construct()
{
$this->someDependency = new SomeDependency();
}
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
final class SomeClass
{
private SomeDependency $someDependency;
public function __construct()
{
$this->someDependency = new SomeDependency();
}
}
CODE_SAMPLE
)]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes() : array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node) : ?Node
{
$allowDynamicPropertiesAttribute = $this->attributeFinder->findAttributeByClass($node, 'AllowDynamicProperties');
if (!$allowDynamicPropertiesAttribute instanceof Attribute) {
return null;
}
if ($this->shouldSkipClass($node)) {
return null;
}
// 1. remove dynamic attribute, most likely any
$node->attrGroups = [];
// 2. add defined @property explicitly
$classPhpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if ($classPhpDocInfo instanceof PhpDocInfo) {
$propertyPhpDocTagNodes = $classPhpDocInfo->getTagsByName('property');
$newProperties = $this->createNewPropertyFromPropertyTagValueNodes($propertyPhpDocTagNodes, $node);
// remove property tags
foreach ($propertyPhpDocTagNodes as $propertyPhpDocTagNode) {
// remove from docblock
$this->phpDocTagRemover->removeTagValueFromNode($classPhpDocInfo, $propertyPhpDocTagNode);
}
// merge new properties to start of the file
$node->stmts = \array_merge($newProperties, $node->stmts);
// update doc info
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
}
return $node;
}
public function provideMinPhpVersion() : int
{
return PhpVersionFeature::DEPRECATE_DYNAMIC_PROPERTIES;
}
/**
* @param PhpDocTagNode[] $propertyPhpDocTagNodes
* @return Property[]
*/
private function createNewPropertyFromPropertyTagValueNodes(array $propertyPhpDocTagNodes, Class_ $class) : array
{
$newProperties = [];
foreach ($propertyPhpDocTagNodes as $propertyPhpDocTagNode) {
// add explicit native property
$propertyTagValueNode = $propertyPhpDocTagNode->value;
if (!$propertyTagValueNode instanceof PropertyTagValueNode) {
continue;
}
$propertyName = \ltrim($propertyTagValueNode->propertyName, '$');
if ($this->isPromotedProperty($class, $propertyName)) {
continue;
}
// is property already defined?
if ($class->getProperty($propertyName)) {
// improve exising one type if needed
$existingProperty = $class->getProperty($propertyName);
if ($existingProperty->type !== null) {
continue;
}
$defaultValue = $existingProperty->props[0]->default;
$isNullable = $defaultValue instanceof Expr && $this->valueResolver->isNull($defaultValue);
$existingProperty->type = $this->typedPropertyFactory->createPropertyTypeNode($propertyTagValueNode, $class, $isNullable);
continue;
}
$newProperties[] = $this->typedPropertyFactory->createFromPropertyTagValueNode($propertyTagValueNode, $class, $propertyName);
}
return $newProperties;
}
private function shouldSkipClass(Class_ $class) : bool
{
// skip magic
$getClassMethod = $class->getMethod('__get');
if ($getClassMethod instanceof ClassMethod) {
return \true;
}
$setClassMethod = $class->getMethod('__set');
if ($setClassMethod instanceof ClassMethod) {
return \true;
}
if (!$class->extends instanceof Node) {
return \false;
}
return !$this->testsNodeAnalyzer->isInTestClass($class);
}
private function isPromotedProperty(Class_ $class, string $propertyName) : bool
{
$constructClassMethod = $class->getMethod(MethodName::CONSTRUCT);
if ($constructClassMethod instanceof ClassMethod) {
foreach ($constructClassMethod->params as $param) {
if (!$param->flags) {
continue;
}
$paramName = $this->getName($param->var);
if ($paramName === $propertyName) {
return \true;
}
}
}
return \false;
}
}
4 changes: 2 additions & 2 deletions src/Application/VersionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '6f863d1552d270c2df41742ffcd87e2168d135b5';
public const PACKAGE_VERSION = 'd7bfc24bedc11374868f5a7ff3066eb847e80a8c';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2024-10-12 20:45:54';
public const RELEASE_DATE = '2024-10-12 17:38:13';
/**
* @var int
*/
Expand Down
2 changes: 2 additions & 0 deletions vendor/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@
'Rector\\CodeQuality\\NodeAnalyzer\\VariableDimFetchAssignResolver' => $baseDir . '/rules/CodeQuality/NodeAnalyzer/VariableDimFetchAssignResolver.php',
'Rector\\CodeQuality\\NodeFactory\\MissingPropertiesFactory' => $baseDir . '/rules/CodeQuality/NodeFactory/MissingPropertiesFactory.php',
'Rector\\CodeQuality\\NodeFactory\\PropertyTypeDecorator' => $baseDir . '/rules/CodeQuality/NodeFactory/PropertyTypeDecorator.php',
'Rector\\CodeQuality\\NodeFactory\\TypedPropertyFactory' => $baseDir . '/rules/CodeQuality/NodeFactory/TypedPropertyFactory.php',
'Rector\\CodeQuality\\NodeManipulator\\ExprBoolCaster' => $baseDir . '/rules/CodeQuality/NodeManipulator/ExprBoolCaster.php',
'Rector\\CodeQuality\\Rector\\Assign\\CombinedAssignRector' => $baseDir . '/rules/CodeQuality/Rector/Assign/CombinedAssignRector.php',
'Rector\\CodeQuality\\Rector\\BooleanAnd\\RemoveUselessIsObjectCheckRector' => $baseDir . '/rules/CodeQuality/Rector/BooleanAnd/RemoveUselessIsObjectCheckRector.php',
Expand All @@ -1106,6 +1107,7 @@
'Rector\\CodeQuality\\Rector\\ClassMethod\\LocallyCalledStaticMethodToNonStaticRector' => $baseDir . '/rules/CodeQuality/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php',
'Rector\\CodeQuality\\Rector\\ClassMethod\\OptionalParametersAfterRequiredRector' => $baseDir . '/rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\CompleteDynamicPropertiesRector' => $baseDir . '/rules/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\DynamicDocBlockPropertyToNativePropertyRector' => $baseDir . '/rules/CodeQuality/Rector/Class_/DynamicDocBlockPropertyToNativePropertyRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyRector' => $baseDir . '/rules/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\StaticToSelfStaticMethodCallOnFinalClassRector' => $baseDir . '/rules/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector.php',
'Rector\\CodeQuality\\Rector\\Concat\\JoinStringConcatRector' => $baseDir . '/rules/CodeQuality/Rector/Concat/JoinStringConcatRector.php',
Expand Down
2 changes: 2 additions & 0 deletions vendor/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,7 @@ class ComposerStaticInit4d4c37b878ce01a3ff505ba7def6aac7
'Rector\\CodeQuality\\NodeAnalyzer\\VariableDimFetchAssignResolver' => __DIR__ . '/../..' . '/rules/CodeQuality/NodeAnalyzer/VariableDimFetchAssignResolver.php',
'Rector\\CodeQuality\\NodeFactory\\MissingPropertiesFactory' => __DIR__ . '/../..' . '/rules/CodeQuality/NodeFactory/MissingPropertiesFactory.php',
'Rector\\CodeQuality\\NodeFactory\\PropertyTypeDecorator' => __DIR__ . '/../..' . '/rules/CodeQuality/NodeFactory/PropertyTypeDecorator.php',
'Rector\\CodeQuality\\NodeFactory\\TypedPropertyFactory' => __DIR__ . '/../..' . '/rules/CodeQuality/NodeFactory/TypedPropertyFactory.php',
'Rector\\CodeQuality\\NodeManipulator\\ExprBoolCaster' => __DIR__ . '/../..' . '/rules/CodeQuality/NodeManipulator/ExprBoolCaster.php',
'Rector\\CodeQuality\\Rector\\Assign\\CombinedAssignRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/Assign/CombinedAssignRector.php',
'Rector\\CodeQuality\\Rector\\BooleanAnd\\RemoveUselessIsObjectCheckRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/BooleanAnd/RemoveUselessIsObjectCheckRector.php',
Expand All @@ -1325,6 +1326,7 @@ class ComposerStaticInit4d4c37b878ce01a3ff505ba7def6aac7
'Rector\\CodeQuality\\Rector\\ClassMethod\\LocallyCalledStaticMethodToNonStaticRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php',
'Rector\\CodeQuality\\Rector\\ClassMethod\\OptionalParametersAfterRequiredRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\CompleteDynamicPropertiesRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\DynamicDocBlockPropertyToNativePropertyRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/Class_/DynamicDocBlockPropertyToNativePropertyRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector.php',
'Rector\\CodeQuality\\Rector\\Class_\\StaticToSelfStaticMethodCallOnFinalClassRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector.php',
'Rector\\CodeQuality\\Rector\\Concat\\JoinStringConcatRector' => __DIR__ . '/../..' . '/rules/CodeQuality/Rector/Concat/JoinStringConcatRector.php',
Expand Down

0 comments on commit 8defa24

Please sign in to comment.