From aaa5ffa8f47ccf9373acc2be874a30b5cf769d11 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Mon, 10 Feb 2020 00:06:17 +0100 Subject: [PATCH] decopule new VendorLock package --- .../AbstractNodeVendorLockResolver.php | 79 +++++ .../ClassMethodParamVendorLockResolver.php} | 81 ++--- .../ClassMethodReturnVendorLockResolver.php | 68 +++++ .../ClassMethodVendorLockResolver.php | 68 +++++ .../PropertyVendorLockResolver.php | 68 +++++ .../vendor-locker/src/VendorLockResolver.php | 281 ++---------------- .../Node/Manipulator/ClassManipulator.php | 4 + 7 files changed, 331 insertions(+), 318 deletions(-) create mode 100644 packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php rename packages/vendor-locker/src/{ReturnNodeVendorLockResolver.php => NodeVendorLocker/ClassMethodParamVendorLockResolver.php} (55%) create mode 100644 packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php create mode 100644 packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php create mode 100644 packages/vendor-locker/src/NodeVendorLocker/PropertyVendorLockResolver.php diff --git a/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php new file mode 100644 index 00000000000..a1d00e16d62 --- /dev/null +++ b/packages/vendor-locker/src/NodeVendorLocker/AbstractNodeVendorLockResolver.php @@ -0,0 +1,79 @@ +parsedNodeCollector = $parsedNodeCollector; + $this->classManipulator = $classManipulator; + $this->nodeNameResolver = $nodeNameResolver; + } + + protected function hasParentClassOrImplementsInterface(ClassLike $classLike): bool + { + if (($classLike instanceof Class_ || $classLike instanceof Interface_) && $classLike->extends) { + return true; + } + + if ($classLike instanceof Class_) { + return (bool) $classLike->implements; + } + + return false; + } + + /** + * @param Class_|Interface_ $classLike + */ + protected function isMethodVendorLockedByInterface(ClassLike $classLike, string $methodName): bool + { + $interfaceNames = $this->classManipulator->getClassLikeNodeParentInterfaceNames($classLike); + + foreach ($interfaceNames as $interfaceName) { + if (! interface_exists($interfaceName)) { + continue; + } + + $interfaceMethods = get_class_methods($interfaceName); + if (! in_array($methodName, $interfaceMethods, true)) { + continue; + } + + return true; + } + + return false; + } +} diff --git a/packages/vendor-locker/src/ReturnNodeVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php similarity index 55% rename from packages/vendor-locker/src/ReturnNodeVendorLockResolver.php rename to packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php index 01b0955ae13..2a6f83a3795 100644 --- a/packages/vendor-locker/src/ReturnNodeVendorLockResolver.php +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodParamVendorLockResolver.php @@ -2,49 +2,19 @@ declare(strict_types=1); -namespace Rector\VendorLocker; +namespace Rector\VendorLocker\NodeVendorLocker; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Interface_; -use Rector\Core\NodeContainer\NodeCollector\ParsedNodeCollector; -use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator; -use Rector\NodeNameResolver\NodeNameResolver; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Node\AttributeKey; -/** - * @todo decouple to standalone package "packages/vendor-locker" - */ -final class ReturnNodeVendorLockResolver +final class ClassMethodParamVendorLockResolver extends AbstractNodeVendorLockResolver { - /** - * @var NodeNameResolver - */ - private $nodeNameResolver; - - /** - * @var ClassManipulator - */ - private $classManipulator; - - /** - * @var ParsedNodeCollector - */ - private $parsedNodeCollector; - - public function __construct( - NodeNameResolver $nodeNameResolver, - ParsedNodeCollector $parsedNodeCollector, - ClassManipulator $classManipulator - ) { - $this->nodeNameResolver = $nodeNameResolver; - $this->parsedNodeCollector = $parsedNodeCollector; - $this->classManipulator = $classManipulator; - } - - public function isVendorLocked(ClassMethod $classMethod): bool + public function isVendorLocked(ClassMethod $classMethod, int $paramPosition): bool { + /** @var Class_|null $classNode */ $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); if ($classNode === null) { return false; @@ -54,15 +24,20 @@ final class ReturnNodeVendorLockResolver return false; } - /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); + if (! is_string($methodName)) { + throw new ShouldNotHappenException(); + } // @todo extract to some "inherited parent method" service /** @var string|null $parentClassName */ $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); if ($parentClassName !== null) { - return $this->isVendorLockedByParentClass($parentClassName, $methodName); + $vendorLock = $this->isParentClassVendorLocking($paramPosition, $parentClassName, $methodName); + if ($vendorLock !== null) { + return $vendorLock; + } } $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); @@ -71,24 +46,10 @@ final class ReturnNodeVendorLockResolver } $interfaceNames = $this->classManipulator->getClassLikeNodeParentInterfaceNames($classNode); - - return $this->isVendorLockedByInterface($interfaceNames, $methodName); + return $this->isInterfaceParamVendorLockin($interfaceNames, $methodName); } - private function hasParentClassOrImplementsInterface(ClassLike $classLike): bool - { - if (($classLike instanceof Class_ || $classLike instanceof Interface_) && $classLike->extends) { - return true; - } - - if ($classLike instanceof Class_) { - return (bool) $classLike->implements; - } - - return false; - } - - private function isVendorLockedByParentClass(string $parentClassName, string $methodName): bool + private function isParentClassVendorLocking(int $paramPosition, string $parentClassName, string $methodName): ?bool { $parentClassNode = $this->parsedNodeCollector->findClass($parentClassName); if ($parentClassNode !== null) { @@ -96,12 +57,13 @@ final class ReturnNodeVendorLockResolver // @todo validate type is conflicting // parent class method in local scope → it's ok if ($parentMethodNode !== null) { - return $parentMethodNode->returnType !== null; + // parent method has no type → we cannot change it here + return isset($parentMethodNode->params[$paramPosition]) && $parentMethodNode->params[$paramPosition]->type === null; } - - // if not, look for it's parent parent - @todo recursion } + // if not, look for it's parent parent - @todo recursion + if (method_exists($parentClassName, $methodName)) { // @todo validate type is conflicting // parent class method in external scope → it's not ok @@ -110,13 +72,10 @@ final class ReturnNodeVendorLockResolver // if not, look for it's parent parent - @todo recursion } - return false; + return null; } - /** - * @param string[] $interfaceNames - */ - private function isVendorLockedByInterface(array $interfaceNames, string $methodName): bool + private function isInterfaceParamVendorLockin(array $interfaceNames, string $methodName): bool { foreach ($interfaceNames as $interfaceName) { $interface = $this->parsedNodeCollector->findInterface($interfaceName); diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php new file mode 100644 index 00000000000..b76d5f103c3 --- /dev/null +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodReturnVendorLockResolver.php @@ -0,0 +1,68 @@ +getAttribute(AttributeKey::CLASS_NODE); + if ($classNode === null) { + return false; + } + + if (! $this->hasParentClassOrImplementsInterface($classNode)) { + return false; + } + + /** @var string $methodName */ + $methodName = $this->nodeNameResolver->getName($classMethod); + + // @todo extract to some "inherited parent method" service + /** @var string|null $parentClassName */ + $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); + + if ($parentClassName !== null) { + return $this->isVendorLockedByParentClass($parentClassName, $methodName); + } + + $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); + if (! $classNode instanceof Class_ && ! $classNode instanceof Interface_) { + return false; + } + + return $this->isMethodVendorLockedByInterface($classNode, $methodName); + } + + private function isVendorLockedByParentClass(string $parentClassName, string $methodName): bool + { + $parentClassNode = $this->parsedNodeCollector->findClass($parentClassName); + if ($parentClassNode !== null) { + $parentMethodNode = $parentClassNode->getMethod($methodName); + // @todo validate type is conflicting + // parent class method in local scope → it's ok + if ($parentMethodNode !== null) { + return $parentMethodNode->returnType !== null; + } + + // if not, look for it's parent parent - @todo recursion + } + + if (method_exists($parentClassName, $methodName)) { + // @todo validate type is conflicting + // parent class method in external scope → it's not ok + return true; + + // if not, look for it's parent parent - @todo recursion + } + + return false; + } +} diff --git a/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php new file mode 100644 index 00000000000..460d65ac047 --- /dev/null +++ b/packages/vendor-locker/src/NodeVendorLocker/ClassMethodVendorLockResolver.php @@ -0,0 +1,68 @@ +nodeNameResolver->getName($classMethod); + if (! is_string($classMethodName)) { + throw new ShouldNotHappenException(); + } + + /** @var Class_|null $class */ + $class = $classMethod->getAttribute(AttributeKey::CLASS_NODE); + if ($class === null) { + return false; + } + + if ($this->isMethodVendorLockedByInterface($class, $classMethodName)) { + return true; + } + + if ($class->extends === null) { + return false; + } + + /** @var string $className */ + $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); + + $classParents = class_parents($className); + foreach ($classParents as $classParent) { + if (! class_exists($classParent)) { + continue; + } + + $parentClassReflection = new ReflectionClass($classParent); + if (! $parentClassReflection->hasMethod($classMethodName)) { + continue; + } + + $methodReflection = $parentClassReflection->getMethod($classMethodName); + if (! $methodReflection->isAbstract()) { + continue; + } + + return true; + } + + return false; + } +} diff --git a/packages/vendor-locker/src/NodeVendorLocker/PropertyVendorLockResolver.php b/packages/vendor-locker/src/NodeVendorLocker/PropertyVendorLockResolver.php new file mode 100644 index 00000000000..b47f97896bf --- /dev/null +++ b/packages/vendor-locker/src/NodeVendorLocker/PropertyVendorLockResolver.php @@ -0,0 +1,68 @@ +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(); + } + + // @todo 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); + + // @todo 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 - @todo recursion + + if (property_exists($parentClassName, $propertyName)) { + // @todo validate type is conflicting + // parent class property in external scope → it's not ok + return true; + + // if not, look for it's parent parent - @todo recursion + } + } + + return false; + } + + private function findParentProperty(string $parentClassName, string $propertyName): ?Property + { + $parentClassNode = $this->parsedNodeCollector->findClass($parentClassName); + if ($parentClassNode === null) { + return null; + } + + return $this->classManipulator->getProperty($parentClassNode, $propertyName); + } +} diff --git a/packages/vendor-locker/src/VendorLockResolver.php b/packages/vendor-locker/src/VendorLockResolver.php index 799fe8e7f7b..19726dbe42c 100644 --- a/packages/vendor-locker/src/VendorLockResolver.php +++ b/packages/vendor-locker/src/VendorLockResolver.php @@ -4,141 +4,60 @@ declare(strict_types=1); namespace Rector\VendorLocker; -use PhpParser\Node; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Property; -use Rector\Core\Exception\ShouldNotHappenException; -use Rector\Core\NodeContainer\NodeCollector\ParsedNodeCollector; -use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator; -use Rector\NodeNameResolver\NodeNameResolver; -use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionClass; +use Rector\VendorLocker\NodeVendorLocker\ClassMethodParamVendorLockResolver; +use Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnVendorLockResolver; +use Rector\VendorLocker\NodeVendorLocker\ClassMethodVendorLockResolver; +use Rector\VendorLocker\NodeVendorLocker\PropertyVendorLockResolver; -/** - * @todo decouple to standalone package "packages/vendor-locker" - */ final class VendorLockResolver { /** - * @var NodeNameResolver + * @var ClassMethodReturnVendorLockResolver */ - private $nodeNameResolver; + private $classMethodReturnVendorLockResolver; /** - * @var ClassManipulator + * @var ClassMethodParamVendorLockResolver */ - private $classManipulator; + private $classMethodParamVendorLockResolver; /** - * @var ParsedNodeCollector + * @var PropertyVendorLockResolver */ - private $parsedNodeCollector; + private $propertyVendorLockResolver; /** - * @var ReturnNodeVendorLockResolver + * @var ClassMethodVendorLockResolver */ - private $returnNodeVendorLockResolver; + private $classMethodVendorLockResolver; public function __construct( - NodeNameResolver $nodeNameResolver, - ParsedNodeCollector $parsedNodeCollector, - ClassManipulator $classManipulator, - ReturnNodeVendorLockResolver $returnNodeVendorLockResolver + ClassMethodReturnVendorLockResolver $classMethodReturnVendorLockResolver, + ClassMethodParamVendorLockResolver $classMethodParamVendorLockResolver, + PropertyVendorLockResolver $propertyVendorLockResolver, + ClassMethodVendorLockResolver $classMethodVendorLockResolver ) { - $this->nodeNameResolver = $nodeNameResolver; - $this->parsedNodeCollector = $parsedNodeCollector; - $this->classManipulator = $classManipulator; - $this->returnNodeVendorLockResolver = $returnNodeVendorLockResolver; + $this->classMethodReturnVendorLockResolver = $classMethodReturnVendorLockResolver; + $this->classMethodParamVendorLockResolver = $classMethodParamVendorLockResolver; + $this->propertyVendorLockResolver = $propertyVendorLockResolver; + $this->classMethodVendorLockResolver = $classMethodVendorLockResolver; } public function isParamChangeVendorLockedIn(ClassMethod $classMethod, int $paramPosition): bool { - /** @var Class_|null $classNode */ - $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if ($classNode === null) { - return false; - } - - if (! $this->hasParentClassOrImplementsInterface($classNode)) { - return false; - } - - $methodName = $this->nodeNameResolver->getName($classMethod); - if (! is_string($methodName)) { - throw new ShouldNotHappenException(); - } - - // @todo extract to some "inherited parent method" service - /** @var string|null $parentClassName */ - $parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME); - - if ($parentClassName !== null) { - $vendorLock = $this->isParentClassVendorLocking($paramPosition, $parentClassName, $methodName); - if ($vendorLock !== null) { - return $vendorLock; - } - } - - $classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if (! $classNode instanceof Class_ && ! $classNode instanceof Interface_) { - return false; - } - - $interfaceNames = $this->classManipulator->getClassLikeNodeParentInterfaceNames($classNode); - return $this->isInterfaceParamVendorLockin($interfaceNames, $methodName); + return $this->classMethodParamVendorLockResolver->isVendorLocked($classMethod, $paramPosition); } public function isReturnChangeVendorLockedIn(ClassMethod $classMethod): bool { - return $this->returnNodeVendorLockResolver->isVendorLocked($classMethod); + return $this->classMethodReturnVendorLockResolver->isVendorLocked($classMethod); } public function isPropertyChangeVendorLockedIn(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(); - } - - // @todo 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); - - // @todo 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 - @todo recursion - - if (property_exists($parentClassName, $propertyName)) { - // @todo validate type is conflicting - // parent class property in external scope → it's not ok - return true; - - // if not, look for it's parent parent - @todo recursion - } - } - - return false; + return $this->propertyVendorLockResolver->isVendorLocked($property); } /** @@ -151,158 +70,6 @@ final class VendorLockResolver */ public function isClassMethodRemovalVendorLocked(ClassMethod $classMethod): bool { - $classMethodName = $this->nodeNameResolver->getName($classMethod); - if (! is_string($classMethodName)) { - throw new ShouldNotHappenException(); - } - - /** @var Class_|null $class */ - $class = $classMethod->getAttribute(AttributeKey::CLASS_NODE); - if ($class === null) { - return false; - } - - if ($this->isVendorLockedByInterface($class, $classMethodName)) { - return true; - } - - if ($class->extends === null) { - return false; - } - - /** @var string $className */ - $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - - $classParents = class_parents($className); - foreach ($classParents as $classParent) { - if (! class_exists($classParent)) { - continue; - } - - $parentClassReflection = new ReflectionClass($classParent); - if (! $parentClassReflection->hasMethod($classMethodName)) { - continue; - } - - $methodReflection = $parentClassReflection->getMethod($classMethodName); - if (! $methodReflection->isAbstract()) { - continue; - } - - return true; - } - - return false; - } - - private function hasParentClassOrImplementsInterface(Node $classNode): bool - { - if (($classNode instanceof Class_ || $classNode instanceof Interface_) && $classNode->extends) { - return true; - } - - if ($classNode instanceof Class_) { - return (bool) $classNode->implements; - } - - return false; - } - - // Until we have getProperty (https://github.com/nikic/PHP-Parser/pull/646) - private function getProperty(ClassLike $classLike, string $name) - { - $lowerName = strtolower($name); - - foreach ($classLike->getProperties() as $property) { - foreach ($property->props as $propertyProperty) { - if ($lowerName !== $propertyProperty->name->toLowerString()) { - continue; - } - - return $property; - } - } - - return null; - } - - private function findParentProperty(string $parentClassName, string $propertyName): ?Property - { - $parentClassNode = $this->parsedNodeCollector->findClass($parentClassName); - if ($parentClassNode === null) { - return null; - } - - return $this->getProperty($parentClassNode, $propertyName); - } - - private function isVendorLockedByInterface(Class_ $class, string $classMethodName): bool - { - // required by interface? - foreach ($class->implements as $implement) { - $implementedInterfaceName = $this->nodeNameResolver->getName($implement); - if (! is_string($implementedInterfaceName)) { - throw new ShouldNotHappenException(); - } - - if (! interface_exists($implementedInterfaceName)) { - continue; - } - - $interfaceMethods = get_class_methods($implementedInterfaceName); - if (! in_array($classMethodName, $interfaceMethods, true)) { - continue; - } - - return true; - } - - return false; - } - - private function isParentClassVendorLocking(int $paramPosition, string $parentClassName, string $methodName): ?bool - { - $parentClassNode = $this->parsedNodeCollector->findClass($parentClassName); - if ($parentClassNode !== null) { - $parentMethodNode = $parentClassNode->getMethod($methodName); - // @todo validate type is conflicting - // parent class method in local scope → it's ok - if ($parentMethodNode !== null) { - // parent method has no type → we cannot change it here - return isset($parentMethodNode->params[$paramPosition]) && $parentMethodNode->params[$paramPosition]->type === null; - } - } - - // if not, look for it's parent parent - @todo recursion - - if (method_exists($parentClassName, $methodName)) { - // @todo validate type is conflicting - // parent class method in external scope → it's not ok - return true; - - // if not, look for it's parent parent - @todo recursion - } - - return null; - } - - private function isInterfaceParamVendorLockin(array $interfaceNames, string $methodName): bool - { - foreach ($interfaceNames as $interfaceName) { - $interface = $this->parsedNodeCollector->findInterface($interfaceName); - // parent class method in local scope → it's ok - // @todo validate type is conflicting - if ($interface !== null && $interface->getMethod($methodName) !== null) { - return false; - } - - if (method_exists($interfaceName, $methodName)) { - // parent class method in external scope → it's not ok - // @todo validate type is conflicting - return true; - } - } - - return false; + return $this->classMethodVendorLockResolver->isRemovalVendorLocked($classMethod); } } diff --git a/src/PhpParser/Node/Manipulator/ClassManipulator.php b/src/PhpParser/Node/Manipulator/ClassManipulator.php index e42ddea13b2..b97d600beb9 100644 --- a/src/PhpParser/Node/Manipulator/ClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassManipulator.php @@ -200,6 +200,10 @@ final class ClassManipulator return $usedTraits; } + /** + * @todo + * Waits on https://github.com/nikic/PHP-Parser/pull/646 to be relased + */ public function getProperty(ClassLike $classLike, string $name): ?Property { foreach ($classLike->getProperties() as $property) {