mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-20 23:18:20 +01:00
[PHP 7.4] Prevent child typed property override by abstract
This commit is contained in:
parent
2f5440760d
commit
53e9cc6c06
@ -10,6 +10,7 @@ use PhpParser\Node\Stmt\Interface_;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\NodeCollector\NodeCollector\ParsedNodeCollector;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
abstract class AbstractNodeVendorLockResolver
|
||||
{
|
||||
@ -41,14 +42,19 @@ abstract class AbstractNodeVendorLockResolver
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
protected function hasParentClassOrImplementsInterface(ClassLike $classLike): bool
|
||||
protected function hasParentClassChildrenClassesOrImplementsInterface(ClassLike $classLike): bool
|
||||
{
|
||||
if (($classLike instanceof Class_ || $classLike instanceof Interface_) && $classLike->extends) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($classLike instanceof Class_) {
|
||||
return (bool) $classLike->implements;
|
||||
if ((bool) $classLike->implements) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var Class_ $classLike */
|
||||
return $this->getChildrenClassesByClass($classLike) !== [];
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -72,6 +78,32 @@ abstract class AbstractNodeVendorLockResolver
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getChildrenClassesByClass(Class_ $class): array
|
||||
{
|
||||
$desiredClassName = $class->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if ($desiredClassName === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$childrenClasses = [];
|
||||
foreach (get_declared_classes() as $declaredClass) {
|
||||
if ($declaredClass === $desiredClassName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_a($declaredClass, $desiredClassName, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$childrenClasses[] = $declaredClass;
|
||||
}
|
||||
|
||||
return $childrenClasses;
|
||||
}
|
||||
|
||||
private function hasInterfaceMethod(string $methodName, string $interfaceName): bool
|
||||
{
|
||||
if (! interface_exists($interfaceName)) {
|
||||
|
@ -19,7 +19,7 @@ final class ClassMethodParamVendorLockResolver extends AbstractNodeVendorLockRes
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->hasParentClassOrImplementsInterface($classNode)) {
|
||||
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ final class ClassMethodReturnVendorLockResolver extends AbstractNodeVendorLockRe
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->hasParentClassOrImplementsInterface($classNode)) {
|
||||
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\VendorLocker\NodeVendorLocker;
|
||||
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class PropertyTypeVendorLockResolver extends AbstractNodeVendorLockResolver
|
||||
{
|
||||
public function isVendorLocked(Property $property): bool
|
||||
{
|
||||
/** @var Class_|null $classNode */
|
||||
$classNode = $property->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if ($classNode === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Class_|Interface_ $classNode */
|
||||
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var string|null $propertyName */
|
||||
$propertyName = $this->nodeNameResolver->getName($property);
|
||||
if (! is_string($propertyName)) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
if ($this->isParentClassLocked($classNode, $propertyName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->isChildClassLocked($property, $classNode, $propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_|Interface_ $classLike
|
||||
*/
|
||||
private function isParentClassLocked(ClassLike $classLike, string $propertyName): bool
|
||||
{
|
||||
if (! $classLike instanceof Class_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract to some "inherited parent method" service
|
||||
/** @var string|null $parentClassName */
|
||||
$parentClassName = $classLike->getAttribute(AttributeKey::PARENT_CLASS_NAME);
|
||||
if ($parentClassName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if not, look for it's parent parent - recursion
|
||||
|
||||
if (property_exists($parentClassName, $propertyName)) {
|
||||
// validate type is conflicting
|
||||
// parent class property in external scope → it's not ok
|
||||
return true;
|
||||
|
||||
// if not, look for it's parent parent
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_|Interface_ $classLike
|
||||
*/
|
||||
private function isChildClassLocked(Property $property, ClassLike $classLike, string $propertyName): bool
|
||||
{
|
||||
if (! $classLike instanceof Class_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// is child class locker
|
||||
if ($property->isPrivate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$childrenClassNames = $this->getChildrenClassesByClass($classLike);
|
||||
foreach ($childrenClassNames as $childClassName) {
|
||||
if (property_exists($childClassName, $propertyName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\VendorLocker\NodeVendorLocker;
|
||||
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class PropertyVendorLockResolver extends AbstractNodeVendorLockResolver
|
||||
{
|
||||
public function isVendorLocked(Property $property): bool
|
||||
{
|
||||
/** @var Class_|null $classNode */
|
||||
$classNode = $property->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if ($classNode === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->hasParentClassOrImplementsInterface($classNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var string|null $propertyName */
|
||||
$propertyName = $this->nodeNameResolver->getName($property);
|
||||
if (! is_string($propertyName)) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
// extract to some "inherited parent method" service
|
||||
/** @var string|null $parentClassName */
|
||||
$parentClassName = $classNode->getAttribute(AttributeKey::PARENT_CLASS_NAME);
|
||||
|
||||
if ($parentClassName !== null) {
|
||||
$parentClassProperty = $this->findParentProperty($parentClassName, $propertyName);
|
||||
|
||||
// validate type is conflicting
|
||||
// parent class property in local scope → it's ok
|
||||
if ($parentClassProperty !== null) {
|
||||
return $parentClassProperty->type !== null;
|
||||
}
|
||||
|
||||
// if not, look for it's parent parent - recursion
|
||||
|
||||
if (property_exists($parentClassName, $propertyName)) {
|
||||
// validate type is conflicting
|
||||
// parent class property in external scope → it's not ok
|
||||
return true;
|
||||
|
||||
// if not, look for it's parent parent
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function findParentProperty(string $parentClassName, string $propertyName): ?Property
|
||||
{
|
||||
$parentClassNode = $this->parsedNodeCollector->findClass($parentClassName);
|
||||
if ($parentClassNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $parentClassNode->getProperty($propertyName);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ use PhpParser\Node\Stmt\Property;
|
||||
use Rector\VendorLocker\NodeVendorLocker\ClassMethodParamVendorLockResolver;
|
||||
use Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnVendorLockResolver;
|
||||
use Rector\VendorLocker\NodeVendorLocker\ClassMethodVendorLockResolver;
|
||||
use Rector\VendorLocker\NodeVendorLocker\PropertyVendorLockResolver;
|
||||
use Rector\VendorLocker\NodeVendorLocker\PropertyTypeVendorLockResolver;
|
||||
|
||||
final class VendorLockResolver
|
||||
{
|
||||
@ -25,9 +25,9 @@ final class VendorLockResolver
|
||||
private $classMethodParamVendorLockResolver;
|
||||
|
||||
/**
|
||||
* @var PropertyVendorLockResolver
|
||||
* @var PropertyTypeVendorLockResolver
|
||||
*/
|
||||
private $propertyVendorLockResolver;
|
||||
private $propertyTypeVendorLockResolver;
|
||||
|
||||
/**
|
||||
* @var ClassMethodVendorLockResolver
|
||||
@ -37,12 +37,12 @@ final class VendorLockResolver
|
||||
public function __construct(
|
||||
ClassMethodReturnVendorLockResolver $classMethodReturnVendorLockResolver,
|
||||
ClassMethodParamVendorLockResolver $classMethodParamVendorLockResolver,
|
||||
PropertyVendorLockResolver $propertyVendorLockResolver,
|
||||
PropertyTypeVendorLockResolver $propertyTypeVendorLockResolver,
|
||||
ClassMethodVendorLockResolver $classMethodVendorLockResolver
|
||||
) {
|
||||
$this->classMethodReturnVendorLockResolver = $classMethodReturnVendorLockResolver;
|
||||
$this->classMethodParamVendorLockResolver = $classMethodParamVendorLockResolver;
|
||||
$this->propertyVendorLockResolver = $propertyVendorLockResolver;
|
||||
$this->propertyTypeVendorLockResolver = $propertyTypeVendorLockResolver;
|
||||
$this->classMethodVendorLockResolver = $classMethodVendorLockResolver;
|
||||
}
|
||||
|
||||
@ -60,9 +60,9 @@ final class VendorLockResolver
|
||||
return $this->classMethodReturnVendorLockResolver->isVendorLocked($classMethod);
|
||||
}
|
||||
|
||||
public function isPropertyChangeVendorLockedIn(Property $property): bool
|
||||
public function isPropertyTypeChangeVendorLockedIn(Property $property): bool
|
||||
{
|
||||
return $this->propertyVendorLockResolver->isVendorLocked($property);
|
||||
return $this->propertyTypeVendorLockResolver->isVendorLocked($property);
|
||||
}
|
||||
|
||||
public function isClassMethodRemovalVendorLocked(ClassMethod $classMethod): bool
|
||||
|
@ -124,7 +124,7 @@ PHP
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->vendorLockResolver->isPropertyChangeVendorLockedIn($node)) {
|
||||
if ($this->vendorLockResolver->isPropertyTypeChangeVendorLockedIn($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
use Rector\Php74\Tests\Rector\Property\TypedPropertyRector\Source\AbstractSomeParent;
|
||||
use Rector\Php74\Tests\Rector\Property\TypedPropertyRector\Source\SomeChildOfSomeParent;
|
||||
use Rector\Privatization\Tests\Rector\MethodCall\PrivatizeLocalGetterToPropertyRector\Fixture\SkipParentClass;
|
||||
|
||||
abstract class SkipParentConflicting
|
||||
{
|
||||
/**
|
||||
* @var AbstractSomeParent
|
||||
*/
|
||||
protected $repository;
|
||||
}
|
||||
|
||||
final class SkipParentConflictingChild extends SkipParentConflicting
|
||||
{
|
||||
/**
|
||||
* @var SomeChildOfSomeParent
|
||||
*/
|
||||
protected $repository;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyRector\Source;
|
||||
|
||||
abstract class AbstractSomeParent
|
||||
{
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php74\Tests\Rector\Property\TypedPropertyRector\Source;
|
||||
|
||||
final class SomeChildOfSomeParent extends AbstractSomeParent
|
||||
{
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user