Make use of static reflection from PHPStan (#5665)

* break test without autoload

* PHPStanServiceFactory - add tests directory as analysed paths

* use ReflectionProvider instead of class_exists

* use native reflections for annotation reader

* use native class and method reflections

* add RectorBetterReflectionSourceLocatorFactory

* add DynamicSourceLocator that is shared by PHPStan and Rector

* use ReflectoinProvider instead of class_exists()

* resolve native ClassMethodReflection

* make AnonymousFunctionFactory use reflection

* [DowngradePHP74] Improve DowngradeCovariantReturnTypeRector

* phsptan: avoid ClassReflection

* ProcessCommand: add file infos to source locator

* [DeadCode] Skip RemoveUnusedPublicMethodRector for test

* remove autolaod from tests

* misc

* remove is_a()

* [CI] enable only few dirs to avoid fails

* misc

* [ci-review] Rector Rectify

* [ci-review] Rector Rectify

Co-authored-by: kaizen-ci <info@kaizen-ci.org>
This commit is contained in:
Tomas Votruba 2021-02-28 08:47:48 +01:00 committed by GitHub
parent c480e0468c
commit 4909e63a1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
336 changed files with 3769 additions and 3599 deletions

View File

@ -21,7 +21,11 @@ jobs:
fail-fast: false
matrix:
directories:
- rules
#- rules
- rules/naming
- rules/privatization
- rules/code-quality
- rules/php74
- packages
- src
- tests
@ -54,6 +58,7 @@ jobs:
## First run Rector - here can't be --dry-run !!! it would stop the job with it and not commit anything in the future
- run: bin/rector rectify ${{ matrix.directories }} --ansi --no-progress-bar
- run: vendor/bin/ecs check --match-git-diff --fix --ansi
# see https://github.com/EndBug/add-and-commit

View File

@ -37,7 +37,7 @@
"nette/utils": "^3.2",
"nikic/php-parser": "^4.10.4",
"phpstan/phpdoc-parser": "^0.4.9",
"phpstan/phpstan": "^0.12.76",
"phpstan/phpstan": "^0.12.79",
"phpstan/phpstan-phpunit": "^0.12.17",
"psr/simple-cache": "^1.0",
"sebastian/diff": "^4.0.4",
@ -186,12 +186,11 @@
"rules/autodiscovery/tests/Rector/FileNode/MoveServicesBySuffixToDirectoryRector/Expected",
"rules/cakephp/tests/Rector/FileWithoutNamespace/ImplicitShortClassNameUseStatementRector/Source",
"rules/cakephp/tests/Rector/Namespace_/AppUsesStaticCallToUseStatementRector/Source",
"rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source",
"rules/renaming/tests/Rector/FileWithoutNamespace/PseudoNamespaceToNamespaceRector/Source",
"rules/symfony4/tests/Rector/MethodCall/ContainerGetToConstructorInjectionRector/Source"
],
"files": [
"rules/type-declaration/tests/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector/Source/external_bool_function.php",
"vendor/nette/forms/src/Forms/Controls/SubmitButton.php",
"rules/restoration/tests/Rector/Use_/RestoreFullyQualifiedNameRector/Source/ShortClassOnly.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/AnotherClass.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/Foo.php",

View File

@ -10,11 +10,13 @@ use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\DoctrineAnnotationGenerated\PhpDocNode\ConstantReferenceIdentifierRestorer;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Throwable;
@ -41,14 +43,21 @@ final class NodeAnnotationReader
*/
private $constantReferenceIdentifierRestorer;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
ConstantReferenceIdentifierRestorer $constantReferenceIdentifierRestorer,
NodeNameResolver $nodeNameResolver,
Reader $reader
Reader $reader,
ReflectionProvider $reflectionProvider
) {
$this->reader = $reader;
$this->nodeNameResolver = $nodeNameResolver;
$this->constantReferenceIdentifierRestorer = $constantReferenceIdentifierRestorer;
$this->reflectionProvider = $reflectionProvider;
}
public function readAnnotation(Node $node, string $annotationClass): ?object
@ -71,12 +80,13 @@ final class NodeAnnotationReader
public function readClassAnnotation(Class_ $class, string $annotationClassName): ?object
{
$classReflection = $this->createClassReflectionFromNode($class);
$nativeClassReflection = $classReflection->getNativeReflection();
try {
// covers cases like https://github.com/rectorphp/rector/issues/3046
/** @var object[] $classAnnotations */
$classAnnotations = $this->reader->getClassAnnotations($classReflection);
$classAnnotations = $this->reader->getClassAnnotations($nativeClassReflection);
return $this->matchNextAnnotation($classAnnotations, $annotationClassName, $class);
} catch (AnnotationException $annotationException) {
// unable to load
@ -86,16 +96,16 @@ final class NodeAnnotationReader
public function readPropertyAnnotation(Property $property, string $annotationClassName): ?object
{
$propertyReflection = $this->createPropertyReflectionFromPropertyNode($property);
if (! $propertyReflection instanceof ReflectionProperty) {
return null;
$reflectionProperty = $this->getNativePropertyReflection($property);
if (! $reflectionProperty instanceof ReflectionProperty) {
throw new ShouldNotHappenException();
}
try {
// covers cases like https://github.com/rectorphp/rector/issues/3046
/** @var object[] $propertyAnnotations */
$propertyAnnotations = $this->reader->getPropertyAnnotations($propertyReflection);
$propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
return $this->matchNextAnnotation($propertyAnnotations, $annotationClassName, $property);
} catch (AnnotationException $annotationException) {
// unable to load
@ -111,13 +121,14 @@ final class NodeAnnotationReader
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
$reflectionMethod = new ReflectionMethod($className, $methodName);
$reflectionMethod = $this->resolveNativeClassMethodReflection($className, $methodName);
try {
// covers cases like https://github.com/rectorphp/rector/issues/3046
/** @var object[] $methodAnnotations */
$methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
foreach ($methodAnnotations as $methodAnnotation) {
if (! is_a($methodAnnotation, $annotationClassName, true)) {
continue;
@ -141,14 +152,13 @@ final class NodeAnnotationReader
return null;
}
private function createClassReflectionFromNode(Class_ $class): ReflectionClass
private function createClassReflectionFromNode(Class_ $class): ClassReflection
{
/** @var string $className */
$className = $this->nodeNameResolver->getName($class);
// covers cases like https://github.com/rectorphp/rector/issues/3230#issuecomment-683317288
return new ReflectionClass($className);
return $this->reflectionProvider->getClass($className);
}
/**
@ -175,7 +185,7 @@ final class NodeAnnotationReader
return null;
}
private function createPropertyReflectionFromPropertyNode(Property $property): ?ReflectionProperty
private function getNativePropertyReflection(Property $property): ?ReflectionProperty
{
/** @var string $propertyName */
$propertyName = $this->nodeNameResolver->getName($property);
@ -186,16 +196,35 @@ final class NodeAnnotationReader
// probably fresh node
return null;
}
if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) {
if (! $this->reflectionProvider->hasClass($className)) {
// probably fresh node
return null;
}
try {
return new ReflectionProperty($className, $propertyName);
$classReflection = $this->reflectionProvider->getClass($className);
$scope = $property->getAttribute(AttributeKey::SCOPE);
$propertyReflection = $classReflection->getProperty($propertyName, $scope);
if ($propertyReflection instanceof PhpPropertyReflection) {
return $propertyReflection->getNativeReflection();
}
} catch (Throwable $throwable) {
// in case of PHPUnit property or just-added property
return null;
}
}
private function resolveNativeClassMethodReflection(string $className, string $methodName): ReflectionMethod
{
if (! $this->reflectionProvider->hasClass($className)) {
throw new ShouldNotHappenException();
}
$classReflection = $this->reflectionProvider->getClass($className);
$reflectionClass = $classReflection->getNativeReflection();
return $reflectionClass->getMethod($methodName);
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\BetterPhpDocParser\PhpDocNodeFactory;
use Nette\Utils\Strings;
use PhpParser\Node;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\Annotation\AnnotationItemsResolver;
use Rector\BetterPhpDocParser\AnnotationReader\NodeAnnotationReader;
@ -56,6 +57,11 @@ abstract class AbstractPhpDocNodeFactory
*/
private $objectTypeSpecifier;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
/**
* @required
*/
@ -63,12 +69,14 @@ abstract class AbstractPhpDocNodeFactory
NodeAnnotationReader $nodeAnnotationReader,
AnnotationContentResolver $annotationContentResolver,
AnnotationItemsResolver $annotationItemsResolver,
ObjectTypeSpecifier $objectTypeSpecifier
ObjectTypeSpecifier $objectTypeSpecifier,
ReflectionProvider $reflectionProvider
): void {
$this->nodeAnnotationReader = $nodeAnnotationReader;
$this->annotationContentResolver = $annotationContentResolver;
$this->annotationItemsResolver = $annotationItemsResolver;
$this->objectTypeSpecifier = $objectTypeSpecifier;
$this->reflectionProvider = $reflectionProvider;
}
protected function resolveContentFromTokenIterator(TokenIterator $tokenIterator): string
@ -79,12 +87,12 @@ abstract class AbstractPhpDocNodeFactory
protected function resolveFqnTargetEntity(string $targetEntity, Node $node): string
{
$targetEntity = $this->getCleanedUpTargetEntity($targetEntity);
if (class_exists($targetEntity)) {
if ($this->reflectionProvider->hasClass($targetEntity)) {
return $targetEntity;
}
$namespacedTargetEntity = $node->getAttribute(AttributeKey::NAMESPACE_NAME) . '\\' . $targetEntity;
if (class_exists($namespacedTargetEntity)) {
if ($this->reflectionProvider->hasClass($namespacedTargetEntity)) {
return $namespacedTargetEntity;
}

View File

@ -8,6 +8,7 @@ use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
@ -20,6 +21,16 @@ final class ClassAnnotationMatcher
*/
private $fullyQualifiedNameByHash = [];
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function resolveTagFullyQualifiedName(string $tag, Node $node): string
{
$uniqueHash = $tag . spl_object_hash($node);
@ -48,7 +59,7 @@ final class ClassAnnotationMatcher
$namespace = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
if ($namespace !== null) {
$namespacedTag = $namespace . '\\' . $tag;
if (class_exists($namespacedTag)) {
if ($this->reflectionProvider->hasClass($namespacedTag)) {
return $namespacedTag;
}
}

View File

@ -24,9 +24,7 @@ final class MultilineSpaceFormatPreserver
public function resolveCurrentPhpDocNodeText(Node $node): ?string
{
if ($node instanceof PhpDocTagNode &&
property_exists($node->value, 'description')
) {
if ($node instanceof PhpDocTagNode && property_exists($node->value, 'description')) {
return $node->value->description;
}

View File

@ -31,16 +31,16 @@ abstract class AbstractTagValueNode implements AttributeAwareNodeInterface, PhpD
*/
protected $tagValueNodeConfiguration;
/**
* @var ArrayPartPhpDocTagPrinter
*/
protected $arrayPartPhpDocTagPrinter;
/**
* @var TagValueNodePrinter
*/
protected $tagValueNodePrinter;
/**
* @var ArrayPartPhpDocTagPrinter
*/
private $arrayPartPhpDocTagPrinter;
/**
* @param array<string, mixed> $items
*/

View File

@ -9,7 +9,6 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\ShortNameAwareTagInterface;
use Rector\BetterPhpDocParser\Contract\PhpDocNode\SilentKeyNodeInterface;
use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\AbstractTagValueNode;
use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface;
use Symfony\Component\Routing\Annotation\Route;
/**
* @see \Rector\BetterPhpDocParser\Tests\PhpDocParser\TagValueNodeReprint\TagValueNodeReprintTest
@ -19,7 +18,7 @@ final class SymfonyRouteTagValueNode extends AbstractTagValueNode implements Sho
/**
* @var string
*/
public const CLASS_NAME = Route::class;
public const CLASS_NAME = 'Symfony\Component\Routing\Annotation\Route';
/**
* @var string

View File

@ -14,8 +14,5 @@ class ApiFilter
{
public function __construct($options = [])
{
if(! class_exists($options['value'])) {
throw new ShouldNotHappenException();
}
}
}

View File

@ -4,88 +4,63 @@ declare(strict_types=1);
namespace Rector\FamilyTree\NodeAnalyzer;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionClass;
use ReflectionMethod;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Php\PhpMethodReflection;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
final class ClassChildAnalyzer
{
public function hasChildClassConstructor(Class_ $class): bool
{
$childClasses = $this->getChildClasses($class);
foreach ($childClasses as $childClass) {
if (! class_exists($childClass)) {
continue;
}
$reflectionClass = new ReflectionClass($childClass);
$constructorReflectionMethod = $reflectionClass->getConstructor();
if (! $constructorReflectionMethod instanceof ReflectionMethod) {
continue;
}
if ($constructorReflectionMethod->class !== $childClass) {
continue;
}
return true;
}
return false;
}
public function hasParentClassConstructor(Class_ $class): bool
{
$className = $class->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return false;
}
/** @var string[] $classParents */
$classParents = (array) class_parents($className);
foreach ($classParents as $classParent) {
$parentReflectionClass = new ReflectionClass($classParent);
$constructMethodReflection = $parentReflectionClass->getConstructor();
if (! $constructMethodReflection instanceof ReflectionMethod) {
continue;
}
if ($constructMethodReflection->class !== $classParent) {
continue;
}
return true;
}
return false;
}
/**
* @return class-string[]
* @var FamilyRelationsAnalyzer
*/
private function getChildClasses(Class_ $class): array
private $familyRelationsAnalyzer;
public function __construct(FamilyRelationsAnalyzer $familyRelationsAnalyzer)
{
$className = $class->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return [];
}
$this->familyRelationsAnalyzer = $familyRelationsAnalyzer;
}
$childClasses = [];
foreach (get_declared_classes() as $declaredClass) {
if (! is_a($declaredClass, $className, true)) {
public function hasChildClassMethod(ClassReflection $classReflection, string $methodName): bool
{
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
foreach ($childrenClassReflections as $childClassReflection) {
if (! $childClassReflection->hasMethod($methodName)) {
continue;
}
if ($declaredClass === $className) {
$constructorReflectionMethod = $childClassReflection->getNativeMethod($methodName);
if (! $constructorReflectionMethod instanceof PhpMethodReflection) {
continue;
}
$childClasses[] = $declaredClass;
$methodDeclaringClassReflection = $constructorReflectionMethod->getDeclaringClass();
if ($methodDeclaringClassReflection->getName() === $childClassReflection->getName()) {
return true;
}
}
return $childClasses;
return false;
}
public function hasParentClassMethod(ClassReflection $classReflection, string $methodName): bool
{
foreach ($classReflection->getParents() as $parentClassReflections) {
if (! $parentClassReflections->hasMethod($methodName)) {
continue;
}
$constructMethodReflection = $parentClassReflections->getNativeMethod($methodName);
if (! $constructMethodReflection instanceof PhpMethodReflection) {
continue;
}
$methodDeclaringMethodClass = $constructMethodReflection->getDeclaringClass();
if ($methodDeclaringMethodClass->getName() === $parentClassReflections->getName()) {
return true;
}
}
return false;
}
}

View File

@ -7,6 +7,9 @@ namespace Rector\FamilyTree\NodeAnalyzer;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\NodeCollector\NodeCollector\NodeRepository;
@ -54,16 +57,26 @@ final class PropertyUsageAnalyzer
return false;
}
$classLike = $property->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike instanceof Class_ && $classLike->isFinal()) {
$scope = $property->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
throw new ShouldNotHappenException();
}
if ($classReflection->isClass() && $classReflection->isFinal()) {
return false;
}
$propertyName = $this->nodeNameResolver->getName($property);
$childrenClassNames = $this->familyRelationsAnalyzer->getChildrenOfClass($className);
foreach ($childrenClassNames as $childClassName) {
$childClass = $this->nodeRepository->findClass($childClassName);
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
foreach ($childrenClassReflections as $childClassReflection) {
$childClass = $this->nodeRepository->findClass($childClassReflection->getName());
if (! $childClass instanceof Class_) {
continue;
}

View File

@ -4,43 +4,46 @@ declare(strict_types=1);
namespace Rector\FamilyTree\Reflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
final class FamilyRelationsAnalyzer
{
/**
* @return class-string[]
* @var ReflectionProvider
*/
public function getChildrenOfClass(string $parentClass): array
private $reflectionProvider;
/**
* @var PrivatesAccessor
*/
private $privatesAccessor;
public function __construct(ReflectionProvider $reflectionProvider, PrivatesAccessor $privatesAccessor)
{
$childrenClasses = [];
foreach (get_declared_classes() as $declaredClass) {
if ($declaredClass === $parentClass) {
continue;
}
if (! is_a($declaredClass, $parentClass, true)) {
continue;
}
$childrenClasses[] = $declaredClass;
}
return $childrenClasses;
$this->reflectionProvider = $reflectionProvider;
$this->privatesAccessor = $privatesAccessor;
}
public function isParentClass(string $class): bool
/**
* @return ClassReflection[]
*/
public function getChildrenOfClassReflection(ClassReflection $desiredClassReflection): array
{
foreach (get_declared_classes() as $declaredClass) {
if ($declaredClass === $class) {
/** @var ClassReflection[] $classReflections */
$classReflections = $this->privatesAccessor->getPrivateProperty($this->reflectionProvider, 'classes');
$childrenClassReflections = [];
foreach ($classReflections as $classReflection) {
if (! $classReflection->isSubclassOf($desiredClassReflection->getName())) {
continue;
}
if (! is_a($declaredClass, $class, true)) {
continue;
}
return true;
$childrenClassReflections[] = $classReflection;
}
return false;
return $childrenClassReflections;
}
}

View File

@ -26,7 +26,9 @@ use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
@ -51,7 +53,7 @@ use ReflectionMethod;
final class NodeRepository
{
/**
* @var array<string, ClassMethod[]>
* @var array<class-string, ClassMethod[]>
*/
private $classMethodsByType = [];
@ -66,13 +68,13 @@ final class NodeRepository
private $funcCallsByName = [];
/**
* @var array<string, array<array<MethodCall|StaticCall>>>
* @var array<class-string, array<array<MethodCall|StaticCall>>>
*/
private $callsByTypeAndMethod = [];
/**
* E.g. [$this, 'someLocalMethod']
* @var ArrayCallable[][][]
* @var array<string, array<string, ArrayCallable[]>>
*/
private $arrayCallablesByTypeAndMethod = [];
@ -121,13 +123,19 @@ final class NodeRepository
*/
private $names = [];
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
ArrayCallableMethodReferenceAnalyzer $arrayCallableMethodReferenceAnalyzer,
ParsedPropertyFetchNodeCollector $parsedPropertyFetchNodeCollector,
NodeNameResolver $nodeNameResolver,
ParsedClassConstFetchNodeCollector $parsedClassConstFetchNodeCollector,
ParsedNodeCollector $parsedNodeCollector,
TypeUnwrapper $typeUnwrapper
TypeUnwrapper $typeUnwrapper,
ReflectionProvider $reflectionProvider
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->arrayCallableMethodReferenceAnalyzer = $arrayCallableMethodReferenceAnalyzer;
@ -135,6 +143,7 @@ final class NodeRepository
$this->parsedClassConstFetchNodeCollector = $parsedClassConstFetchNodeCollector;
$this->parsedNodeCollector = $parsedNodeCollector;
$this->typeUnwrapper = $typeUnwrapper;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -200,7 +209,7 @@ final class NodeRepository
}
/**
* @return MethodCall[][]|StaticCall[][]
* @return array<string, MethodCall[]|StaticCall[]>
*/
public function findMethodCallsOnClass(string $className): array
{
@ -226,7 +235,12 @@ final class NodeRepository
$classMethods = [];
foreach ($this->classMethodsByType as $className => $classMethodByMethodName) {
if (! is_a($className, $desiredType, true)) {
if (! $this->reflectionProvider->hasClass($className)) {
continue;
}
$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->isSubclassOf($desiredType)) {
continue;
}
@ -271,15 +285,14 @@ final class NodeRepository
return $this->classMethodsByType[$className][$methodName];
}
$parentClass = $className;
if (! class_exists($parentClass)) {
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}
while ($parentClass = get_parent_class($parentClass)) {
if (isset($this->classMethodsByType[$parentClass][$methodName])) {
return $this->classMethodsByType[$parentClass][$methodName];
$classReflection = $this->reflectionProvider->getClass($className);
foreach ($classReflection->getParents() as $parentClassReflection) {
if (isset($this->classMethodsByType[$parentClassReflection->getName()][$methodName])) {
return $this->classMethodsByType[$parentClassReflection->getName()][$methodName];
}
}
@ -366,36 +379,46 @@ final class NodeRepository
}
/**
* @return string[]
* @return ClassReflection[]
*/
public function findDirectClassConstantFetches(string $desiredClassName, string $desiredConstantName): array
public function findDirectClassConstantFetches(ClassReflection $classReflection, string $desiredConstantName): array
{
$classConstantFetchByClassAndName = $this->parsedClassConstFetchNodeCollector->getClassConstantFetchByClassAndName();
return $classConstantFetchByClassAndName[$desiredClassName][$desiredConstantName] ?? [];
$classTypes = $classConstantFetchByClassAndName[$classReflection->getName()][$desiredConstantName] ?? [];
return $this->resolveClassReflectionsFromClassTypes($classTypes);
}
/**
* @return string[]
* @return ClassReflection[]
*/
public function findIndirectClassConstantFetches(string $desiredClassName, string $desiredConstantName): array
{
public function findIndirectClassConstantFetches(
ClassReflection $classReflection,
string $desiredConstantName
): array {
$classConstantFetchByClassAndName = $this->parsedClassConstFetchNodeCollector->getClassConstantFetchByClassAndName();
foreach ($classConstantFetchByClassAndName as $className => $classesByConstantName) {
if (! $this->reflectionProvider->hasClass($className)) {
return [];
}
$currentClassReflection = $this->reflectionProvider->getClass($className);
if (! isset($classesByConstantName[$desiredConstantName])) {
continue;
}
if (! $classReflection->isSubclassOf($currentClassReflection->getName()) &&
! $currentClassReflection->isSubclassOf($classReflection->getName())) {
continue;
}
// include child usages and parent usages
if (! is_a($className, $desiredClassName, true) && ! is_a($desiredClassName, $className, true)) {
if ($currentClassReflection->getName() === $classReflection->getName()) {
continue;
}
if ($desiredClassName === $className) {
continue;
}
return $classesByConstantName[$desiredConstantName];
return $this->resolveClassReflectionsFromClassTypes($classesByConstantName[$desiredConstantName]);
}
return [];
@ -638,6 +661,17 @@ final class NodeRepository
return $this->parsedNodeCollector->getClassConstFetches();
}
public function resolveCallerClassName(MethodCall $methodCall): ?string
{
$callerType = $this->nodeTypeResolver->getStaticType($methodCall->var);
$callerObjectType = $this->typeUnwrapper->unwrapFirstObjectTypeFromUnionType($callerType);
if (! $callerObjectType instanceof TypeWithClassName) {
return null;
}
return $callerObjectType->getClassName();
}
private function collectArray(Array_ $array): void
{
$arrayCallable = $this->arrayCallableMethodReferenceAnalyzer->match($array);
@ -645,7 +679,16 @@ final class NodeRepository
return;
}
if (! $arrayCallable->isExistingMethod()) {
if (! $this->reflectionProvider->hasClass($arrayCallable->getClass())) {
return;
}
$classReflection = $this->reflectionProvider->getClass($arrayCallable->getClass());
if (! $classReflection->isClass()) {
return;
}
if (! $classReflection->hasMethod($arrayCallable->getMethod())) {
return;
}
@ -655,6 +698,7 @@ final class NodeRepository
private function addMethod(ClassMethod $classMethod): void
{
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
// anonymous
if ($className === null) {
return;
@ -704,10 +748,20 @@ final class NodeRepository
return false;
}
if (! is_a($currentClassName, $desiredClass, true)) {
if (! $this->reflectionProvider->hasClass($desiredClass)) {
return false;
}
if (! $this->reflectionProvider->hasClass($currentClassName)) {
return false;
}
$desiredClassReflection = $this->reflectionProvider->getClass($desiredClass);
$currentClassReflection = $this->reflectionProvider->getClass($currentClassName);
if (! $currentClassReflection->isSubclassOf($desiredClassReflection->getName())) {
return false;
}
return $currentClassName !== $desiredClass;
}
@ -731,23 +785,11 @@ final class NodeRepository
return $implementerInterfaces;
}
private function resolveCallerClassName(MethodCall $methodCall): ?string
{
$callerType = $this->nodeTypeResolver->getStaticType($methodCall->var);
$callerObjectType = $this->typeUnwrapper->unwrapFirstObjectTypeFromUnionType($callerType);
if (! $callerObjectType instanceof TypeWithClassName) {
return null;
}
return $callerObjectType->getClassName();
}
private function resolveNodeClassTypes(Node $node): Type
{
if ($node instanceof MethodCall && $node->var instanceof Variable && $node->var->name === 'this') {
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className) {
return new ObjectType($className);
}
@ -781,4 +823,22 @@ final class NodeRepository
}
}
}
/**
* @param class-string[] $classTypes
* @return ClassReflection[]
*/
private function resolveClassReflectionsFromClassTypes(array $classTypes): array
{
$classReflections = [];
foreach ($classTypes as $classType) {
if (! $this->reflectionProvider->hasClass($classType)) {
continue;
}
$classReflections[] = $this->reflectionProvider->getClass($classType);
}
return $classReflections;
}
}

View File

@ -6,6 +6,7 @@ namespace Rector\NodeCollector\NodeCollector;
use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
@ -13,12 +14,11 @@ use PHPStan\Type\UnionType;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use ReflectionClass;
final class ParsedClassConstFetchNodeCollector
{
/**
* @var string[][][]
* @var array<string, array<string, class-string[]>>
*/
private $classConstantFetchByClassAndName = [];
@ -32,9 +32,15 @@ final class ParsedClassConstFetchNodeCollector
*/
private $nodeTypeResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(NodeNameResolver $nodeNameResolver, ReflectionProvider $reflectionProvider)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -70,6 +76,7 @@ final class ParsedClassConstFetchNodeCollector
}
// current class
/** @var string $classOfUse */
$classOfUse = $node->getAttribute(AttributeKey::CLASS_NAME);
$this->classConstantFetchByClassAndName[$className][$constantName][] = $classOfUse;
@ -80,7 +87,7 @@ final class ParsedClassConstFetchNodeCollector
}
/**
* @return string[][][]
* @return array<string, array<string, class-string[]>>
*/
public function getClassConstantFetchByClassAndName(): array
{
@ -137,14 +144,17 @@ final class ParsedClassConstFetchNodeCollector
*/
private function getConstantsDefinedInClass(string $className): array
{
$reflectionClass = new ReflectionClass($className);
if (! $this->reflectionProvider->hasClass($className)) {
return [];
}
$classReflection = $this->reflectionProvider->getClass($className);
$reflectionClass = $classReflection->getNativeReflection();
$constants = $reflectionClass->getConstants();
$currentClassConstants = array_keys($constants);
$parentClassReflection = $reflectionClass->getParentClass();
if (! $parentClassReflection) {
if ($classReflection->getParentClass() !== false) {
return $currentClassConstants;
}

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Native\NativeMethodReflection;
use PHPStan\Reflection\ParameterReflection;
@ -21,7 +22,6 @@ use Rector\Core\ValueObject\MethodName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use ReflectionMethod;
final class MethodReflectionProvider
{
@ -70,7 +70,7 @@ final class MethodReflectionProvider
return $parameterTypes;
}
public function provideByMethodCall(MethodCall $methodCall): ?ReflectionMethod
public function provideByMethodCall(MethodCall $methodCall): ?MethodReflection
{
$className = $methodCall->getAttribute(AttributeKey::CLASS_NAME);
if (! is_string($className)) {
@ -82,21 +82,16 @@ final class MethodReflectionProvider
return null;
}
if (! method_exists($className, $methodName)) {
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}
return new ReflectionMethod($className, $methodName);
}
public function provideByClassAndMethodName(string $class, string $method, Scope $scope): ?MethodReflection
{
$classReflection = $this->reflectionProvider->getClass($class);
if (! $classReflection->hasMethod($method)) {
$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->hasMethod($methodName)) {
return null;
}
return $classReflection->getMethod($method, $scope);
return $classReflection->getNativeMethod($methodName);
}
/**
@ -155,7 +150,12 @@ final class MethodReflectionProvider
return null;
}
return $this->provideByClassAndMethodName($class, $method, $scope);
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return null;
}
return $classReflection->getMethod($method, $scope);
}
/**
@ -163,9 +163,8 @@ final class MethodReflectionProvider
*/
public function getParameterReflectionsFromMethodReflection(MethodReflection $methodReflection): array
{
$methodReflectionVariant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
return $methodReflectionVariant->getParameters();
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
return $parametersAcceptor->getParameters();
}
/**
@ -174,18 +173,27 @@ final class MethodReflectionProvider
public function provideParameterNamesByNew(New_ $new): array
{
$objectType = $this->nodeTypeResolver->resolve($new->class);
$classes = TypeUtils::getDirectClassNames($objectType);
$parameterNames = [];
foreach ($classes as $class) {
if (! method_exists($class, MethodName::CONSTRUCT)) {
if (! $this->reflectionProvider->hasClass($class)) {
continue;
}
$methodReflection = new ReflectionMethod($class, MethodName::CONSTRUCT);
$classReflection = $this->reflectionProvider->getClass($class);
if (! $classReflection->hasMethod(MethodName::CONSTRUCT)) {
continue;
}
$nativeClassReflection = $classReflection->getNativeReflection();
$methodReflection = $nativeClassReflection->getMethod(MethodName::CONSTRUCT);
foreach ($methodReflection->getParameters() as $reflectionParameter) {
$parameterNames[] = $reflectionParameter->name;
$parameterNames[] = $reflectionParameter->getName();
}
}
@ -207,7 +215,9 @@ final class MethodReflectionProvider
}
foreach ($classes as $class) {
$methodReflection = $this->provideByClassAndMethodName($class, $methodName, $scope);
$classReflection = $this->reflectionProvider->getClass($class);
$methodReflection = $classReflection->getMethod($methodName, $scope);
if ($methodReflection instanceof MethodReflection) {
return $methodReflection;
}

View File

@ -5,9 +5,10 @@ declare(strict_types=1);
namespace Rector\NodeCollector;
use Nette\Utils\Strings;
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use ReflectionClass;
final class StaticAnalyzer
{
@ -16,9 +17,15 @@ final class StaticAnalyzer
*/
private $nodeRepository;
public function __construct(NodeRepository $nodeRepository)
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(NodeRepository $nodeRepository, ReflectionProvider $reflectionProvider)
{
$this->nodeRepository = $nodeRepository;
$this->reflectionProvider = $reflectionProvider;
}
public function isStaticMethod(string $methodName, string $className): bool
@ -30,29 +37,33 @@ final class StaticAnalyzer
// could be static in doc type magic
// @see https://regex101.com/r/tlvfTB/1
if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) {
if (! $this->reflectionProvider->hasClass($className)) {
return false;
}
$reflectionClass = new ReflectionClass($className);
if ($this->hasStaticAnnotation($methodName, $reflectionClass)) {
$classReflection = $this->reflectionProvider->getClass($className);
if ($this->hasStaticAnnotation($methodName, $classReflection)) {
return true;
}
// probably magic method → we don't know
if (! method_exists($className, $methodName)) {
if (! $classReflection->hasMethod($methodName)) {
return false;
}
$methodReflection = $reflectionClass->getMethod($methodName);
$methodReflection = $classReflection->getNativeMethod($methodName);
return $methodReflection->isStatic();
}
private function hasStaticAnnotation(string $methodName, ReflectionClass $reflectionClass): bool
private function hasStaticAnnotation(string $methodName, ClassReflection $classReflection): bool
{
$resolvedPhpDocBlock = $classReflection->getResolvedPhpDoc();
if (! $resolvedPhpDocBlock instanceof ResolvedPhpDocBlock) {
return false;
}
return (bool) Strings::match(
(string) $reflectionClass->getDocComment(),
$resolvedPhpDocBlock->getPhpDocString(),
'#@method\s*static\s*(.*?)\b' . $methodName . '\b#'
);
}

View File

@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Rector\NodeCollector\ValueObject;
use ReflectionMethod;
final class ArrayCallable
{
/**
@ -33,14 +31,4 @@ final class ArrayCallable
{
return $this->method;
}
public function isExistingMethod(): bool
{
return method_exists($this->class, $this->method);
}
public function getReflectionMethod(): ReflectionMethod
{
return new ReflectionMethod($this->class, $this->method);
}
}

View File

@ -17,6 +17,7 @@ use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\CodingStyle\Naming\ClassNaming;
use Rector\Core\Contract\Rector\RectorInterface;
@ -61,6 +62,11 @@ final class NodeNameResolver
*/
private $betterStandardPrinter;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
/**
* @param NodeNameResolverInterface[] $nodeNameResolvers
*/
@ -69,6 +75,7 @@ final class NodeNameResolver
BetterStandardPrinter $betterStandardPrinter,
CurrentFileInfoProvider $currentFileInfoProvider,
ClassNaming $classNaming,
ReflectionProvider $reflectionProvider,
array $nodeNameResolvers = []
) {
$this->regexPatternDetector = $regexPatternDetector;
@ -76,6 +83,7 @@ final class NodeNameResolver
$this->currentFileInfoProvider = $currentFileInfoProvider;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->classNaming = $classNaming;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -331,17 +339,17 @@ final class NodeNameResolver
}
/**
* @param string[] $desiredClassNames
* @param ObjectType[] $desiredObjectTypes
*/
public function isInClassNames(Node $node, array $desiredClassNames): bool
public function isInClassNames(Node $node, array $desiredObjectTypes): bool
{
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode === null) {
return false;
}
foreach ($desiredClassNames as $desiredClassName) {
if (is_a($className, $desiredClassName, true)) {
foreach ($desiredObjectTypes as $desiredObjectType) {
if ($this->isInClassNamed($classNode, $desiredObjectType)) {
return true;
}
}
@ -356,7 +364,16 @@ final class NodeNameResolver
return false;
}
return is_a($className, $objectType->getClassName(), true);
if (! $this->reflectionProvider->hasClass($className)) {
return false;
}
$classReflection = $this->reflectionProvider->getClass($className);
if ($classReflection->getName() === $objectType->getClassName()) {
return true;
}
return $classReflection->isSubclassOf($objectType->getClassName());
}
/**

View File

@ -9,11 +9,22 @@ use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeNameResolver\Contract\NodeNameResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class FuncCallNameResolver implements NodeNameResolverInterface
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function getNode(): string
{
return FuncCall::class;
@ -39,7 +50,8 @@ final class FuncCallNameResolver implements NodeNameResolverInterface
$namespaceName = $functionName->getAttribute(AttributeKey::NAMESPACED_NAME);
if ($namespaceName instanceof FullyQualified) {
$functionFqnName = $namespaceName->toString();
if (function_exists($functionFqnName)) {
if ($this->reflectionProvider->hasFunction($namespaceName, null)) {
return $functionFqnName;
}
}

View File

@ -13,6 +13,8 @@ use Rector\Core\Php\TypeAnalyzer;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
@ -29,7 +31,9 @@ return static function (ContainerConfigurator $containerConfigurator): void {
->autoconfigure();
$services->load('Rector\NodeTypeResolver\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/Contract']);
->exclude([__DIR__ . '/../src/Contract', __DIR__ . '/../src/Reflection/BetterReflection']);
$services->set(IntermediateSourceLocator::class);
$services->set(TypeAnalyzer::class);
$services->set(FilesFinder::class);
@ -48,5 +52,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(TypeNodeResolver::class)
->factory([service(PHPStanServicesFactory::class), 'createTypeNodeResolver']);
$services->set(DynamicSourceLocatorProvider::class)
->factory([service(PHPStanServicesFactory::class), 'createDynamicSourceLocatorProvider']);
$services->set(NodeConnectingVisitor::class);
};

View File

@ -0,0 +1,15 @@
parameters:
# see https://github.com/rectorphp/rector/issues/3490#issue-634342324
featureToggles:
disableRuntimeReflectionProvider: false
services:
- Rector\NodeTypeResolver\Reflection\BetterReflection\RectorBetterReflectionSourceLocatorFactory
- Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator
- Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider
# basically decorates native PHPStan source locator with a dynamic source locator that is also available in Rector DI
betterReflectionSourceLocator:
class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator
factory: ['@Rector\NodeTypeResolver\Reflection\BetterReflection\RectorBetterReflectionSourceLocatorFactory', 'create']
autowired: false

View File

@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver;
final class ClassExistenceStaticHelper
{
public static function doesClassLikeExist(string $classLike): bool
{
if (class_exists($classLike)) {
return true;
}
if (interface_exists($classLike)) {
return true;
}
return trait_exists($classLike);
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Contract;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
interface SourceLocatorProviderInterface
{
public function provide(): SourceLocator;
}

View File

@ -15,6 +15,7 @@ use PHPStan\File\FileHelper;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Configuration\Option;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
/**
@ -34,6 +35,8 @@ final class PHPStanServicesFactory
$additionalConfigFiles = [];
$additionalConfigFiles[] = $parameterProvider->provideStringParameter(Option::PHPSTAN_FOR_RECTOR_PATH);
$additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/static-reflection.neon';
$additionalConfigFiles[] = __DIR__ . '/../../config/phpstan/better-infer.neon';
$existingAdditionalConfigFiles = array_filter($additionalConfigFiles, 'file_exists');
@ -103,4 +106,12 @@ final class PHPStanServicesFactory
{
return $this->container->getByType(TypeNodeResolver::class);
}
/**
* @api
*/
public function createDynamicSourceLocatorProvider(): DynamicSourceLocatorProvider
{
return $this->container->getByType(DynamicSourceLocatorProvider::class);
}
}

View File

@ -4,23 +4,23 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeTypeCorrector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker;
final class GenericClassStringTypeCorrector
{
/**
* @var ClassLikeExistenceChecker
* @var ReflectionProvider
*/
private $classLikeExistenceChecker;
private $reflectionProvider;
public function __construct(ClassLikeExistenceChecker $classLikeExistenceChecker)
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->classLikeExistenceChecker = $classLikeExistenceChecker;
$this->reflectionProvider = $reflectionProvider;
}
public function correct(Type $mainType): Type
@ -31,7 +31,7 @@ final class GenericClassStringTypeCorrector
return $traverse($type);
}
if (! $this->classLikeExistenceChecker->doesClassLikeExist($type->getValue())) {
if (! $this->reflectionProvider->hasClass($type->getValue())) {
return $traverse($type);
}

View File

@ -8,7 +8,6 @@ use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\TypeWithClassName;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\NodeTypeResolver\Reflection\ClassReflectionTypesResolver;
@ -42,7 +41,7 @@ final class ParentClassLikeTypeCorrector
public function correct(Type $type): Type
{
if ($type instanceof TypeWithClassName) {
if (! ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) {
if (! $this->reflectionProvider->hasClass($type->getClassName())) {
return $type;
}
@ -54,7 +53,7 @@ final class ParentClassLikeTypeCorrector
$allTypes = [];
foreach ($classNames as $className) {
if (! ClassExistenceStaticHelper::doesClassLikeExist($className)) {
if (! $this->reflectionProvider->hasClass($className)) {
continue;
}

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
@ -34,7 +33,6 @@ use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\NodeAnalyzer\ClassAnalyzer;
use Rector\Core\Util\StaticInstanceOf;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector;
@ -144,7 +142,6 @@ final class NodeTypeResolver
// this should also work with ObjectType and UnionType with ObjectType
// use PHPStan types here
if ($resolvedType->equals($requiredObjectType)) {
return true;
}
@ -155,6 +152,15 @@ final class NodeTypeResolver
public function resolve(Node $node): Type
{
$type = $this->resolveFirstType($node);
if ($type instanceof IntersectionType) {
foreach ($type->getTypes() as $intersectionedType) {
if ($intersectionedType instanceof TypeWithClassName) {
return $this->parentClassLikeTypeCorrector->correct($intersectionedType);
}
}
}
if (! $type instanceof TypeWithClassName) {
return $type;
}
@ -187,24 +193,24 @@ final class NodeTypeResolver
*/
public function getStaticType(Node $node): Type
{
if ($this->isArrayExpr($node)) {
/** @var Expr $node */
return $this->resolveArrayType($node);
}
if ($node instanceof Arg) {
throw new ShouldNotHappenException('Arg cannot have type, use $arg->value instead');
}
if (StaticInstanceOf::isOneOf($node, [Param::class, Scalar::class])) {
if ($node instanceof Param) {
return $this->resolve($node);
}
$nodeScope = $node->getAttribute(AttributeKey::SCOPE);
if (! $node instanceof Expr) {
return new MixedType();
}
if (! $nodeScope instanceof Scope) {
if ($this->arrayTypeAnalyzer->isArrayType($node)) {
return $this->resolveArrayType($node);
}
if ($node instanceof Scalar) {
return $this->resolve($node);
}
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return new MixedType();
}
@ -215,7 +221,7 @@ final class NodeTypeResolver
}
}
$staticType = $nodeScope->getType($node);
$staticType = $scope->getType($node);
if (! $staticType instanceof ObjectType) {
return $staticType;
}
@ -228,9 +234,13 @@ final class NodeTypeResolver
if ($this->isStaticType($node, IntegerType::class)) {
return true;
}
return $this->isStaticType($node, FloatType::class);
}
/**
* @param class-string<Type> $staticTypeClass
*/
public function isStaticType(Node $node, string $staticTypeClass): bool
{
if (! is_a($staticTypeClass, Type::class, true)) {
@ -389,7 +399,9 @@ final class NodeTypeResolver
continue;
}
return true;
if ($unionedType->equals($requiredObjectType)) {
return true;
}
}
return false;
@ -432,14 +444,6 @@ final class NodeTypeResolver
return $this->resolveFirstType($node->var);
}
private function isArrayExpr(Node $node): bool
{
if (! $node instanceof Expr) {
return false;
}
return $this->arrayTypeAnalyzer->isArrayType($node);
}
private function resolveArrayType(Expr $expr): Type
{
/** @var Scope|null $scope */

View File

@ -27,7 +27,7 @@ final class ArrayDimFetchTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -26,7 +26,7 @@ final class CastTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -35,7 +35,7 @@ final class ClassAndInterfaceTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -26,7 +26,7 @@ final class ClassConstFetchTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -31,7 +31,7 @@ final class ClassMethodOrClassConstTypeResolver implements NodeTypeResolverInter
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -7,6 +7,7 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
@ -20,7 +21,17 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
final class NameTypeResolver implements NodeTypeResolverInterface
{
/**
* @return string[]
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
/**
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{
@ -45,22 +56,31 @@ final class NameTypeResolver implements NodeTypeResolverInterface
*/
private function resolveParent(Name $name): Type
{
/** @var string|null $parentClassName */
$parentClassName = $name->getAttribute(AttributeKey::PARENT_CLASS_NAME);
// missing parent class, probably unused parent:: call
if ($parentClassName === null) {
$className = $name->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return new MixedType();
}
$type = new ObjectType($parentClassName);
$parentParentClass = get_parent_class($parentClassName);
if ($parentParentClass) {
$type = new UnionType([$type, new ObjectType($parentParentClass)]);
if (! $this->reflectionProvider->hasClass($className)) {
return new MixedType();
}
return $type;
$classReflection = $this->reflectionProvider->getClass($className);
$parentClassObjectTypes = [];
foreach ($classReflection->getParents() as $parentClassReflection) {
$parentClassObjectTypes[] = new ObjectType($parentClassReflection->getName());
}
if ($parentClassObjectTypes === []) {
return new MixedType();
}
if (count($parentClassObjectTypes) === 1) {
return $parentClassObjectTypes[0];
}
return new UnionType($parentClassObjectTypes);
}
private function resolveFullyQualifiedName(Name $name): string

View File

@ -75,7 +75,7 @@ final class ParamTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -5,32 +5,19 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\Property;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\VarLikeIdentifier;
use PhpParser\Parser;
use PHPStan\Analyser\Scope;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Symplify\SmartFileSystem\SmartFileSystem;
/**
* @see \Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\PropertyFetchTypeResolver\PropertyFetchTypeResolverTest
@ -47,52 +34,24 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface
*/
private $nodeNameResolver;
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
/**
* @var TraitNodeScopeCollector
*/
private $traitNodeScopeCollector;
/**
* @var SmartFileSystem
* @var ReflectionProvider
*/
private $smartFileSystem;
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var Parser
*/
private $parser;
/**
* @var NodeRepository
*/
private $nodeRepository;
private $reflectionProvider;
public function __construct(
NodeNameResolver $nodeNameResolver,
StaticTypeMapper $staticTypeMapper,
TraitNodeScopeCollector $traitNodeScopeCollector,
SmartFileSystem $smartFileSystem,
BetterNodeFinder $betterNodeFinder,
Parser $parser,
NodeRepository $nodeRepository
ReflectionProvider $reflectionProvider
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->staticTypeMapper = $staticTypeMapper;
$this->traitNodeScopeCollector = $traitNodeScopeCollector;
$this->betterNodeFinder = $betterNodeFinder;
$this->smartFileSystem = $smartFileSystem;
$this->parser = $parser;
$this->nodeRepository = $nodeRepository;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -104,7 +63,7 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{
@ -152,79 +111,29 @@ final class PropertyFetchTypeResolver implements NodeTypeResolverInterface
private function getVendorPropertyFetchType(PropertyFetch $propertyFetch): Type
{
$varObjectType = $this->nodeTypeResolver->resolve($propertyFetch->var);
if (! $varObjectType instanceof TypeWithClassName) {
return new MixedType();
}
$class = $this->nodeRepository->findClass($varObjectType->getClassName());
if ($class !== null) {
return new MixedType();
}
// 3rd party code
$propertyName = $this->nodeNameResolver->getName($propertyFetch->name);
if ($propertyName === null) {
return new MixedType();
}
if (! property_exists($varObjectType->getClassName(), $propertyName)) {
$varType = $this->nodeTypeResolver->resolve($propertyFetch->var);
if (! $varType instanceof ObjectType) {
return new MixedType();
}
$phpDocInfo = $propertyFetch->getAttribute(AttributeKey::PHP_DOC_INFO);
if (! $phpDocInfo instanceof PhpDocInfo && $varObjectType instanceof ObjectType) {
return $this->getPropertyPropertyResolution($varObjectType, $propertyName);
}
return $this->getTypeFromPhpDocInfo($phpDocInfo);
}
private function getPropertyPropertyResolution(ObjectType $varObjectType, string $propertyName): Type
{
$classReflection = $varObjectType->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return $varObjectType;
}
if ($classReflection->isBuiltIn()) {
return $varObjectType;
}
$nodes = $this->parser->parse($this->smartFileSystem->readFile((string) $classReflection->getFileName()));
$propertyProperty = $this->betterNodeFinder->findFirst($nodes, function (Node $node) use (
$propertyName
): bool {
if (! $node instanceof PropertyProperty) {
return false;
}
if (! $node->name instanceof VarLikeIdentifier) {
return false;
}
return $node->name->toString() === $propertyName;
});
if ($propertyProperty instanceof PropertyProperty) {
return $this->nodeTypeResolver->resolve($propertyProperty);
}
return new MixedType();
}
private function getTypeFromPhpDocInfo(PhpDocInfo $phpDocInfo): Type
{
$tagValueNode = $phpDocInfo->getVarTagValueNode();
if (! $tagValueNode instanceof VarTagValueNode) {
if (! $this->reflectionProvider->hasClass($varType->getClassName())) {
return new MixedType();
}
$typeNode = $tagValueNode->type;
if (! $typeNode instanceof TypeNode) {
$classReflection = $this->reflectionProvider->getClass($varType->getClassName());
if (! $classReflection->hasProperty($propertyName)) {
return new MixedType();
}
return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, new Nop());
$propertyFetchScope = $propertyFetch->getAttribute(AttributeKey::SCOPE);
$propertyReflection = $classReflection->getProperty($propertyName, $propertyFetchScope);
return $propertyReflection->getReadableType();
}
}

View File

@ -32,7 +32,7 @@ final class PropertyTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -7,8 +7,10 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -26,9 +28,15 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(NodeNameResolver $nodeNameResolver, ReflectionProvider $reflectionProvider)
{
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -40,7 +48,7 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{
@ -60,15 +68,26 @@ final class StaticCallTypeResolver implements NodeTypeResolverInterface
return $classType;
}
$classNames = TypeUtils::getDirectClassNames($classType);
if (! $classType instanceof ObjectType) {
return $classType;
}
if (! $this->reflectionProvider->hasClass($classType->getClassName())) {
return $classType;
}
$classReflection = $this->reflectionProvider->getClass($classType->getClassName());
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return $classType;
}
foreach ($classNames as $className) {
if (! method_exists($className, $methodName)) {
/** @var ClassReflection[] $currentAndParentClassReflections */
$currentAndParentClassReflections = array_merge([$classReflection], $classReflection->getParents());
foreach ($currentAndParentClassReflections as $currentAndParentClassReflection) {
if (! $currentAndParentClassReflection->hasMethod($methodName)) {
continue;
}

View File

@ -6,12 +6,12 @@ namespace Rector\NodeTypeResolver\NodeTypeResolver;
use PhpParser\Node;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface;
use ReflectionClass;
/**
* @see \Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\TraitTypeResolver\TraitTypeResolverTest
@ -19,7 +19,17 @@ use ReflectionClass;
final class TraitTypeResolver implements NodeTypeResolverInterface
{
/**
* @return string[]
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
/**
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{
@ -31,12 +41,17 @@ final class TraitTypeResolver implements NodeTypeResolverInterface
*/
public function resolve(Node $traitNode): Type
{
$reflectionClass = new ReflectionClass((string) $traitNode->namespacedName);
$traitName = (string) $traitNode->namespacedName;
if (! $this->reflectionProvider->hasClass($traitName)) {
return new MixedType();
}
$classReflection = $this->reflectionProvider->getClass($traitName);
$types = [];
$types[] = new ObjectType($reflectionClass->getName());
$types[] = new ObjectType($traitName);
foreach ($reflectionClass->getTraits() as $usedTraitReflection) {
foreach ($classReflection->getTraits() as $usedTraitReflection) {
$types[] = new ObjectType($usedTraitReflection->getName());
}

View File

@ -59,7 +59,7 @@ final class VariableTypeResolver implements NodeTypeResolverInterface
}
/**
* @return string[]
* @return array<class-string<Node>>
*/
public function getNodeClasses(): array
{

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\Generic\GenericObjectType;
@ -54,11 +53,7 @@ final class TypeHasher
}
if ($type instanceof ConstantType) {
if (method_exists($type, 'getValue')) {
return get_class($type) . $type->getValue();
}
throw new ShouldNotHappenException();
return get_class($type) . $type->getValue();
}
if ($type instanceof UnionType) {

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Reflection\BetterReflection;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator;
final class RectorBetterReflectionSourceLocatorFactory
{
/**
* @var BetterReflectionSourceLocatorFactory
*/
private $betterReflectionSourceLocatorFactory;
/**
* @var IntermediateSourceLocator
*/
private $intermediateSourceLocator;
public function __construct(
BetterReflectionSourceLocatorFactory $betterReflectionSourceLocatorFactory,
IntermediateSourceLocator $intermediateSourceLocator
) {
$this->betterReflectionSourceLocatorFactory = $betterReflectionSourceLocatorFactory;
$this->intermediateSourceLocator = $intermediateSourceLocator;
}
public function create(): SourceLocator
{
$phpStanSourceLocator = $this->betterReflectionSourceLocatorFactory->create();
return new AggregateSourceLocator([$this->intermediateSourceLocator, $phpStanSourceLocator]);
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator;
use PHPStan\BetterReflection\Identifier\Identifier;
use PHPStan\BetterReflection\Identifier\IdentifierType;
use PHPStan\BetterReflection\Reflection\Reflection;
use PHPStan\BetterReflection\Reflector\Reflector;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use Rector\NodeTypeResolver\Contract\SourceLocatorProviderInterface;
final class IntermediateSourceLocator implements SourceLocator
{
/**
* @var SourceLocatorProviderInterface[]
*/
private $sourceLocatorProviders = [];
/**
* @param SourceLocatorProviderInterface[] $sourceLocatorProviders
*/
public function __construct(array $sourceLocatorProviders)
{
$this->sourceLocatorProviders = $sourceLocatorProviders;
}
public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
{
foreach ($this->sourceLocatorProviders as $sourceLocatorProvider) {
$sourceLocator = $sourceLocatorProvider->provide();
$reflection = $sourceLocator->locateIdentifier($reflector, $identifier);
if ($reflection instanceof Reflection) {
return $reflection;
}
}
return null;
}
/**
* Find all identifiers of a type
* @return Reflection[]
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
{
foreach ($this->sourceLocatorProviders as $sourceLocatorProvider) {
$sourceLocator = $sourceLocatorProvider->provide();
$reflections = $sourceLocator->locateIdentifiersByType($reflector, $identifierType);
if ($reflections !== []) {
return $reflections;
}
}
return [];
}
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher;
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocator;
use Rector\NodeTypeResolver\Contract\SourceLocatorProviderInterface;
use Symplify\SmartFileSystem\SmartFileInfo;
final class DynamicSourceLocatorProvider implements SourceLocatorProviderInterface
{
/**
* @var SmartFileInfo[]
*/
private $fileInfos = [];
/**
* @var FileNodesFetcher
*/
private $fileNodesFetcher;
public function __construct(FileNodesFetcher $fileNodesFetcher)
{
$this->fileNodesFetcher = $fileNodesFetcher;
}
public function setFileInfo(SmartFileInfo $fileInfo): void
{
$this->fileInfos = [$fileInfo];
}
/**
* @param SmartFileInfo[] $fileInfos
*/
public function addFileInfos(array $fileInfos): void
{
$this->fileInfos = array_merge($this->fileInfos, $fileInfos);
}
public function provide(): SourceLocator
{
$sourceLocators = [];
foreach ($this->fileInfos as $fileInfo) {
$sourceLocators[] = new OptimizedSingleFileSourceLocator($this->fileNodesFetcher, $fileInfo->getRealPath());
}
return new AggregateSourceLocator($sourceLocators);
}
}

View File

@ -12,6 +12,7 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover;
use Rector\PhpAttribute\Contract\PhpAttributableTagNodeInterface;
@ -35,14 +36,21 @@ final class AnnotationToAttributeConverter
*/
private $phpDocTagRemover;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
PhpAttributeGroupFactory $phpAttributeGroupFactory,
PhpDocInfoFactory $phpDocInfoFactory,
PhpDocTagRemover $phpDocTagRemover
PhpDocTagRemover $phpDocTagRemover,
ReflectionProvider $reflectionProvider
) {
$this->phpAttributeGroupFactory = $phpAttributeGroupFactory;
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->phpDocTagRemover = $phpDocTagRemover;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -96,7 +104,7 @@ final class AnnotationToAttributeConverter
return array_filter(
$phpAttributableTagNodes,
function (PhpAttributableTagNodeInterface $phpAttributableTagNode): bool {
return class_exists($phpAttributableTagNode->getAttributeClassName());
return $this->reflectionProvider->hasClass($phpAttributableTagNode->getAttributeClassName());
}
);
}

View File

@ -4,16 +4,17 @@ declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeAnalyzer;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Stmt;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector;
@ -29,10 +30,7 @@ final class UnionTypeCommonTypeNarrower
BinaryOp::class => [BinaryOp::class, Expr::class],
Expr::class => [Node::class, Expr::class],
Stmt::class => [Node::class, Stmt::class],
'PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode' => [
'PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode',
'PHPStan\PhpDocParser\Ast\Node',
],
PhpDocTagValueNode::class => [PhpDocTagValueNode::class, \PHPStan\PhpDocParser\Ast\Node::class],
];
/**
@ -40,9 +38,17 @@ final class UnionTypeCommonTypeNarrower
*/
private $genericClassStringTypeCorrector;
public function __construct(GenericClassStringTypeCorrector $genericClassStringTypeCorrector)
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
GenericClassStringTypeCorrector $genericClassStringTypeCorrector,
ReflectionProvider $reflectionProvider
) {
$this->genericClassStringTypeCorrector = $genericClassStringTypeCorrector;
$this->reflectionProvider = $reflectionProvider;
}
public function narrowToSharedObjectType(UnionType $unionType): ?ObjectType
@ -79,9 +85,15 @@ final class UnionTypeCommonTypeNarrower
return $unionType;
}
if ($unionedType->getGenericType() instanceof TypeWithClassName) {
$availableTypes[] = $this->resolveClassParentClassesAndInterfaces($unionedType->getGenericType());
$genericClassStrings = [];
if ($unionedType->getGenericType() instanceof ObjectType) {
$parentClassReflections = $this->resolveClassParentClassesAndInterfaces($unionedType->getGenericType());
foreach ($parentClassReflections as $classReflection) {
$genericClassStrings[] = $classReflection->getName();
}
}
$availableTypes[] = $genericClassStrings;
}
$genericClassStringType = $this->createGenericClassStringType($availableTypes);
@ -100,57 +112,43 @@ final class UnionTypeCommonTypeNarrower
$availableTypes = [];
foreach ($unionType->getTypes() as $unionedType) {
if (! $unionedType instanceof TypeWithClassName) {
if (! $unionedType instanceof ObjectType) {
return [];
}
$availableTypes[] = $this->resolveClassParentClassesAndInterfaces($unionedType);
$typeClassReflections = $this->resolveClassParentClassesAndInterfaces($unionedType);
$typeClassNames = [];
foreach ($typeClassReflections as $classReflection) {
$typeClassNames[] = $classReflection->getName();
}
$availableTypes[] = $typeClassNames;
}
return $this->narrowAvailableTypes($availableTypes);
}
/**
* @return string[]
* @return ClassReflection[]
*/
private function resolveClassParentClassesAndInterfaces(TypeWithClassName $typeWithClassName): array
private function resolveClassParentClassesAndInterfaces(ObjectType $objectType): array
{
$parentClasses = class_parents($typeWithClassName->getClassName());
if ($parentClasses === false) {
$parentClasses = [];
if (! $this->reflectionProvider->hasClass($objectType->getClassName())) {
return [];
}
$implementedInterfaces = class_implements($typeWithClassName->getClassName());
if ($implementedInterfaces === false) {
$implementedInterfaces = [];
}
$implementedInterfaces = $this->filterOutNativeInterfaces($implementedInterfaces);
$classReflection = $this->reflectionProvider->getClass($objectType->getClassName());
// put earliest interfaces first
$implementedInterfaces = array_reverse($implementedInterfaces);
$implementedInterfaceClassReflections = array_reverse($classReflection->getInterfaces());
$classParentClassesAndInterfaces = array_merge($implementedInterfaces, $parentClasses);
/** @var ClassReflection[] $parentClassAndInterfaceReflections */
$parentClassAndInterfaceReflections = array_merge(
$implementedInterfaceClassReflections,
$classReflection->getParents()
);
return array_unique($classParentClassesAndInterfaces);
}
/**
* @param class-string[] $interfaces
* @return class-string[]
*/
private function filterOutNativeInterfaces(array $interfaces): array
{
foreach ($interfaces as $key => $implementedInterface) {
// remove native interfaces
if (Strings::contains($implementedInterface, '\\')) {
continue;
}
unset($interfaces[$key]);
}
return $interfaces;
return $this->filterOutNativeClassReflections($parentClassAndInterfaceReflections);
}
/**
@ -185,4 +183,15 @@ final class UnionTypeCommonTypeNarrower
return null;
}
/**
* @param ClassReflection[] $classReflections
* @return ClassReflection[]
*/
private function filterOutNativeClassReflections(array $classReflections): array
{
return array_filter($classReflections, function (ClassReflection $classReflection): bool {
return ! $classReflection->isBuiltin();
});
}
}

View File

@ -8,6 +8,7 @@ use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
@ -27,7 +28,6 @@ use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower;
use Rector\TypeDeclaration\TypeNormalizer;
use Symplify\PackageBuilder\Reflection\ClassLikeExistenceChecker;
/**
* @see \Rector\PHPStanStaticTypeMapper\Tests\TypeMapper\ArrayTypeMapperTest
@ -55,23 +55,24 @@ final class ArrayTypeMapper implements TypeMapperInterface
private $unionTypeCommonTypeNarrower;
/**
* @var ClassLikeExistenceChecker
* @var ReflectionProvider
*/
private $classLikeExistenceChecker;
private $reflectionProvider;
/**
* To avoid circular dependency
* @required
*/
public function autowireArrayTypeMapper(
PHPStanStaticTypeMapper $phpStanStaticTypeMapper,
TypeNormalizer $typeNormalizer,
UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower,
ClassLikeExistenceChecker $classLikeExistenceChecker
ReflectionProvider $reflectionProvider
): void {
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
$this->typeNormalizer = $typeNormalizer;
$this->unionTypeCommonTypeNarrower = $unionTypeCommonTypeNarrower;
$this->classLikeExistenceChecker = $classLikeExistenceChecker;
$this->reflectionProvider = $reflectionProvider;
}
public function getNodeClass(): string
@ -254,9 +255,7 @@ final class ArrayTypeMapper implements TypeMapperInterface
GenericClassStringType $genericClassStringType
): AttributeAwareNodeInterface {
$genericType = $genericClassStringType->getGenericType();
if ($genericType instanceof ObjectType && ! $this->classLikeExistenceChecker->doesClassLikeExist(
$genericType->getClassName()
)) {
if ($genericType instanceof ObjectType && ! $this->reflectionProvider->hasClass($genericType->getClassName())) {
return new AttributeAwareIdentifierTypeNode($genericType->getClassName());
}

View File

@ -10,13 +10,13 @@ use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareGenericTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterface;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
@ -32,6 +32,16 @@ final class ObjectTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMa
*/
private $phpStanStaticTypeMapper;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function getNodeClass(): string
{
return ObjectType::class;
@ -120,7 +130,7 @@ final class ObjectTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMa
return '\\' . $type->getClassName();
}
if (ClassExistenceStaticHelper::doesClassLikeExist($type->getClassName())) {
if ($this->reflectionProvider->hasClass($type->getClassName())) {
// FQN by default
return '\\' . $type->describe(VerbosityLevel::typeOnly());
}

View File

@ -5,12 +5,11 @@ declare(strict_types=1);
namespace Rector\PostRector\NodeAnalyzer;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Nette\NetteInjectTagNode;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeNameResolver\NodeNameResolver;
use ReflectionClass;
use ReflectionMethod;
final class NetteInjectDetector
{
@ -24,10 +23,19 @@ final class NetteInjectDetector
*/
private $phpDocInfoFactory;
public function __construct(NodeNameResolver $nodeNameResolver, PhpDocInfoFactory $phpDocInfoFactory)
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
NodeNameResolver $nodeNameResolver,
PhpDocInfoFactory $phpDocInfoFactory,
ReflectionProvider $reflectionProvider
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->reflectionProvider = $reflectionProvider;
}
public function isNetteInjectPreferred(Class_ $class): bool
@ -62,11 +70,16 @@ final class NetteInjectDetector
return false;
}
if (! is_a($className, 'Nette\Application\IPresenter', true)) {
if (! $this->reflectionProvider->hasClass($className)) {
return false;
}
// has parent class
$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->isSubclassOf('Nette\Application\IPresenter')) {
return false;
}
// has no parent class
if ($class->extends === null) {
return false;
}
@ -78,18 +91,19 @@ final class NetteInjectDetector
}
// prefer local constructor
$classReflection = new ReflectionClass($className);
$classReflection = $this->reflectionProvider->getClass($className);
if ($classReflection->hasMethod(MethodName::CONSTRUCT)) {
/** @var ReflectionMethod $constructorReflectionMethod */
$constructorReflectionMethod = $classReflection->getConstructor();
$declaringClass = $constructorReflectionMethod->getDeclaringClass();
// be sure its local constructor
if ($constructorReflectionMethod->class === $className) {
if ($declaringClass->getName() === $className) {
return false;
}
}
$classReflection = new ReflectionClass($parentClass);
$classReflection = $this->reflectionProvider->getClass($parentClass);
return $classReflection->hasMethod(MethodName::CONSTRUCT);
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\PostRector\Rector;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ReflectionProvider;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper;
use Rector\CodingStyle\Node\NameImporter;
@ -48,13 +49,19 @@ final class NameImportingPostRector extends NodeVisitorAbstract implements PostR
*/
private $nodeNameResolver;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
ParameterProvider $parameterProvider,
NameImporter $nameImporter,
DocBlockNameImporter $docBlockNameImporter,
ClassNameImportSkipper $classNameImportSkipper,
PhpDocInfoFactory $phpDocInfoFactory,
NodeNameResolver $nodeNameResolver
NodeNameResolver $nodeNameResolver,
ReflectionProvider $reflectionProvider
) {
$this->parameterProvider = $parameterProvider;
$this->nameImporter = $nameImporter;
@ -62,6 +69,7 @@ final class NameImportingPostRector extends NodeVisitorAbstract implements PostR
$this->classNameImportSkipper = $classNameImportSkipper;
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
}
public function enterNode(Node $node): ?Node
@ -102,15 +110,19 @@ final class NameImportingPostRector extends NodeVisitorAbstract implements PostR
if (! is_callable($importName)) {
return $this->nameImporter->importName($name);
}
if (substr_count($name->toCodeString(), '\\') <= 1) {
return $this->nameImporter->importName($name);
}
if (! $this->classNameImportSkipper->isFoundInUse($name)) {
return $this->nameImporter->importName($name);
}
if (function_exists($name->getLast())) {
if ($this->reflectionProvider->hasFunction(new Name($name->getLast()), null)) {
return $this->nameImporter->importName($name);
}
return null;
}
}

View File

@ -92,12 +92,12 @@ final class UseAddingPostRector extends NodeVisitorAbstract implements PostRecto
$this->useNodesToAddCollector->clear($smartFileInfo);
// A. has namespace? add under it
$namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
if ($namespace instanceof Namespace_) {
$foundNode = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
if ($foundNode instanceof Namespace_) {
// first clean
$this->useImportsRemover->removeImportsFromNamespace($namespace, $removedShortUses);
$this->useImportsRemover->removeImportsFromNamespace($foundNode, $removedShortUses);
// then add, to prevent adding + removing false positive of same short use
$this->useImportsAdder->addImportsToNamespace($namespace, $useImportTypes, $functionUseImportTypes);
$this->useImportsAdder->addImportsToNamespace($foundNode, $useImportTypes, $functionUseImportTypes);
return $nodes;
}

View File

@ -6,10 +6,15 @@ namespace Rector\ReadWrite\Guard;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionFunction;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
final class VariableToConstantGuard
{
@ -23,9 +28,24 @@ final class VariableToConstantGuard
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
/**
* @var PrivatesAccessor
*/
private $privatesAccessor;
public function __construct(
NodeNameResolver $nodeNameResolver,
ReflectionProvider $reflectionProvider,
PrivatesAccessor $privatesAccessor
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
$this->privatesAccessor = $privatesAccessor;
}
public function isReadArg(Arg $arg): bool
@ -35,40 +55,50 @@ final class VariableToConstantGuard
return true;
}
$functionName = $this->nodeNameResolver->getName($parentParent);
if ($functionName === null) {
$functionNameString = $this->nodeNameResolver->getName($parentParent);
if ($functionNameString === null) {
return true;
}
if (! function_exists($functionName)) {
$functionName = new Name($functionNameString);
$argScope = $arg->getAttribute(AttributeKey::SCOPE);
if (! $this->reflectionProvider->hasFunction($functionName, $argScope)) {
// we don't know
return true;
}
$referenceParametersPositions = $this->resolveFunctionReferencePositions($functionName);
$functionReflection = $this->reflectionProvider->getFunction($functionName, $argScope);
$referenceParametersPositions = $this->resolveFunctionReferencePositions($functionReflection);
if ($referenceParametersPositions === []) {
// no reference always only write
return true;
}
$argumentPosition = $this->getArgumentPosition($parentParent, $arg);
return ! in_array($argumentPosition, $referenceParametersPositions, true);
}
/**
* @return int[]
*/
private function resolveFunctionReferencePositions(string $functionName): array
private function resolveFunctionReferencePositions(FunctionReflection $functionReflection): array
{
if (isset($this->referencePositionsByFunctionName[$functionName])) {
return $this->referencePositionsByFunctionName[$functionName];
if (isset($this->referencePositionsByFunctionName[$functionReflection->getName()])) {
return $this->referencePositionsByFunctionName[$functionReflection->getName()];
}
// this is needed, as native function reflection does not have access to referenced parameters
if ($functionReflection instanceof NativeFunctionReflection) {
$nativeFunctionReflection = new ReflectionFunction($functionReflection->getName());
} else {
$nativeFunctionReflection = $this->privatesAccessor->getPrivateProperty($functionReflection, 'reflection');
}
$referencePositions = [];
$reflectionFunction = new ReflectionFunction($functionName);
foreach ($reflectionFunction->getParameters() as $position => $reflectionParameter) {
/** @var int $position */
foreach ($nativeFunctionReflection->getParameters() as $position => $reflectionParameter) {
if (! $reflectionParameter->isPassedByReference()) {
continue;
}
@ -76,7 +106,7 @@ final class VariableToConstantGuard
$referencePositions[] = $position;
}
$this->referencePositionsByFunctionName[$functionName] = $referencePositions;
$this->referencePositionsByFunctionName[$functionReflection->getName()] = $referencePositions;
return $referencePositions;
}

View File

@ -29,8 +29,7 @@ final class NodeTypesProvider
public function provide(): array
{
$finder = new Finder();
$finder = $finder
->files()
$finder = $finder->files()
->in(self::PHP_PARSER_NODES_PATH);
$fileInfos = iterator_to_array($finder->getIterator());
@ -44,6 +43,7 @@ final class NodeTypesProvider
if ($reflectionClass->isAbstract()) {
continue;
}
if ($reflectionClass->isInterface()) {
continue;
}

View File

@ -32,15 +32,16 @@ final class PhpParserNodeMapper
if (! is_a($node, $phpParserNodeMapper->getNodeType())) {
continue;
}
// do not let Expr collect all the types
// note: can be solve later with priorities on mapper interface, making this last
if ($phpParserNodeMapper->getNodeType() !== Expr::class) {
return $phpParserNodeMapper->mapToPHPStan($node);
}
if (! is_a($node, String_::class)) {
if (! $node instanceof String_) {
return $phpParserNodeMapper->mapToPHPStan($node);
}
continue;
}
throw new NotImplementedYetException(get_class($node));

View File

@ -6,6 +6,7 @@ namespace Rector\StaticTypeMapper\PhpParser;
use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
@ -15,7 +16,6 @@ use PHPStan\Type\StaticType;
use PHPStan\Type\StringType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PSR4\Collector\RenamedClassesCollector;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
@ -29,9 +29,17 @@ final class NameNodeMapper implements PhpParserNodeMapperInterface
*/
private $renamedClassesCollector;
public function __construct(RenamedClassesCollector $renamedClassesCollector)
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
RenamedClassesCollector $renamedClassesCollector,
ReflectionProvider $reflectionProvider
) {
$this->renamedClassesCollector = $renamedClassesCollector;
$this->reflectionProvider = $reflectionProvider;
}
public function getNodeType(): string
@ -58,7 +66,7 @@ final class NameNodeMapper implements PhpParserNodeMapperInterface
private function isExistingClass(string $name): bool
{
if (ClassExistenceStaticHelper::doesClassLikeExist($name)) {
if ($this->reflectionProvider->hasClass($name)) {
return true;
}
@ -79,6 +87,11 @@ final class NameNodeMapper implements PhpParserNodeMapperInterface
return new StaticType($className);
}
if ($this->reflectionProvider->hasClass($className)) {
$classReflection = $this->reflectionProvider->getClass($className);
return new ThisType($classReflection);
}
return new ThisType($className);
}

View File

@ -20,6 +20,7 @@ use Rector\Core\NonPhpFile\NonPhpFileProcessor;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\Core\Stubs\StubLoader;
use Rector\Core\ValueObject\StaticNonPhpFileSuffixes;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use Rector\Testing\Application\EnabledRectorClassProvider;
use Rector\Testing\Configuration\AllRectorConfigFactory;
use Rector\Testing\Contract\RunnableInterface;
@ -87,11 +88,6 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase
*/
private static $runnableRectorFactory;
/**
* @var bool
*/
private $autoloadTestFixture = true;
/**
* @var RectorConfigsResolver
*/
@ -154,13 +150,6 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase
return StaticFixtureFinder::yieldDirectory($directory, $suffix);
}
protected function doTestFileInfoWithoutAutoload(SmartFileInfo $fileInfo): void
{
$this->autoloadTestFixture = false;
$this->doTestFileInfo($fileInfo);
$this->autoloadTestFixture = true;
}
/**
* @param SmartFileInfo[] $extraFileInfos
*/
@ -170,7 +159,7 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase
$inputFileInfoAndExpectedFileInfo = StaticFixtureSplitter::splitFileInfoToLocalInputAndExpectedFileInfos(
$fixtureFileInfo,
$this->autoloadTestFixture
false
);
$inputFileInfo = $inputFileInfoAndExpectedFileInfo->getInputFileInfo();
@ -180,6 +169,10 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase
$nodeScopeResolver = $this->getService(NodeScopeResolver::class);
$nodeScopeResolver->setAnalysedFiles([$inputFileInfo->getRealPath()]);
/** @var DynamicSourceLocatorProvider $dynamicDirectoryLocatorProvider */
$dynamicDirectoryLocatorProvider = $this->getService(DynamicSourceLocatorProvider::class);
$dynamicDirectoryLocatorProvider->setFileInfo($inputFileInfo);
$expectedFileInfo = $inputFileInfoAndExpectedFileInfo->getExpectedFileInfo();
$this->doTestFileMatchesExpectedContent($inputFileInfo, $expectedFileInfo, $fixtureFileInfo, $extraFileInfos);

View File

@ -4,22 +4,13 @@ declare(strict_types=1);
namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
use Rector\Core\NodeManipulator\ClassManipulator;
use PHPStan\Reflection\ClassReflection;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
abstract class AbstractNodeVendorLockResolver
{
/**
* @var ClassManipulator
*/
protected $classManipulator;
/**
* @var NodeNameResolver
*/
@ -33,80 +24,53 @@ abstract class AbstractNodeVendorLockResolver
/**
* @var FamilyRelationsAnalyzer
*/
private $familyRelationsAnalyzer;
protected $familyRelationsAnalyzer;
/**
* @required
*/
public function autowireAbstractNodeVendorLockResolver(
NodeRepository $nodeRepository,
ClassManipulator $classManipulator,
NodeNameResolver $nodeNameResolver,
FamilyRelationsAnalyzer $familyRelationsAnalyzer
): void {
$this->nodeRepository = $nodeRepository;
$this->classManipulator = $classManipulator;
$this->nodeNameResolver = $nodeNameResolver;
$this->familyRelationsAnalyzer = $familyRelationsAnalyzer;
}
protected function hasParentClassChildrenClassesOrImplementsInterface(ClassLike $classLike): bool
protected function hasParentClassChildrenClassesOrImplementsInterface(ClassReflection $classReflection): bool
{
if (($classLike instanceof Class_ || $classLike instanceof Interface_) && $classLike->extends) {
return true;
}
if ($classLike instanceof Class_) {
if ((bool) $classLike->implements) {
if ($classReflection->isClass()) {
// has at least interface
if ($classReflection->getInterfaces() !== []) {
return true;
}
/** @var Class_ $classLike */
return $this->getChildrenClassesByClass($classLike) !== [];
}
return false;
}
/**
* @param Class_|Interface_ $classLike
*/
protected function isMethodVendorLockedByInterface(ClassLike $classLike, string $methodName): bool
{
$interfaceNames = $this->classManipulator->getClassLikeNodeParentInterfaceNames($classLike);
foreach ($interfaceNames as $interfaceName) {
if (! $this->hasInterfaceMethod($methodName, $interfaceName)) {
continue;
// has at least one parent class
if ($classReflection->getParents() !== []) {
return true;
}
return true;
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
return $childrenClassReflections !== [];
}
if ($classReflection->isInterface()) {
return $classReflection->getInterfaces() !== [];
}
return false;
}
/**
* @return class-string[]
*/
protected function getChildrenClassesByClass(Class_ $class): array
protected function isMethodVendorLockedByInterface(ClassReflection $classReflection, string $methodName): bool
{
$desiredClassName = $class->getAttribute(AttributeKey::CLASS_NAME);
if ($desiredClassName === null) {
return [];
foreach ($classReflection->getInterfaces() as $interfaceReflection) {
if ($interfaceReflection->hasMethod($methodName)) {
return true;
}
}
return $this->familyRelationsAnalyzer->getChildrenOfClass($desiredClassName);
}
private function hasInterfaceMethod(string $methodName, string $interfaceName): bool
{
if (! interface_exists($interfaceName)) {
return false;
}
$interfaceMethods = get_class_methods($interfaceName);
return in_array($methodName, $interfaceMethods, true);
return false;
}
}

View File

@ -4,49 +4,59 @@ declare(strict_types=1);
namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ClassMethodParamVendorLockResolver extends AbstractNodeVendorLockResolver
{
public function isVendorLocked(ClassMethod $classMethod, int $paramPosition): bool
{
$classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if (! $classNode instanceof Class_) {
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) {
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classReflection)) {
return false;
}
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
/** @var string|null $parentClassName */
$parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME);
if ($parentClassName !== null) {
$vendorLock = $this->isParentClassVendorLocking($paramPosition, $parentClassName, $methodName);
if ($classReflection->getParentClass() !== false) {
$vendorLock = $this->isParentClassVendorLocking(
$classReflection->getParentClass(),
$paramPosition,
$methodName
);
if ($vendorLock !== null) {
return $vendorLock;
}
}
$classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode instanceof Class_) {
return $this->isMethodVendorLockedByInterface($classNode, $methodName);
if ($classReflection->isClass()) {
return $this->isMethodVendorLockedByInterface($classReflection, $methodName);
}
if ($classNode instanceof Interface_) {
return $this->isMethodVendorLockedByInterface($classNode, $methodName);
if ($classReflection->isInterface()) {
return $this->isMethodVendorLockedByInterface($classReflection, $methodName);
}
return false;
}
private function isParentClassVendorLocking(int $paramPosition, string $parentClassName, string $methodName): ?bool
{
$parentClass = $this->nodeRepository->findClass($parentClassName);
private function isParentClassVendorLocking(
ClassReflection $parentClassReflection,
int $paramPosition,
string $methodName
): ?bool {
$parentClass = $this->nodeRepository->findClass($parentClassReflection->getName());
if ($parentClass !== null) {
$parentClassMethod = $parentClass->getMethod($methodName);
// parent class method in local scope → it's ok
@ -58,7 +68,8 @@ final class ClassMethodParamVendorLockResolver extends AbstractNodeVendorLockRes
return $parentClassMethod->params[$paramPosition]->type === null;
}
}
if (method_exists($parentClassName, $methodName)) {
if ($parentClassReflection->hasMethod($methodName)) {
// parent class method in external scope → it's not ok
// if not, look for it's parent parent
return true;

View File

@ -6,17 +6,20 @@ namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
//use PHPStan\Analyser\Scope;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeVisitor;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
@ -24,10 +27,10 @@ use Rector\NodeTypeResolver\NodeTypeResolver;
final class ClassMethodReturnTypeOverrideGuard
{
/**
* @var array<string, array<string>>
* @var array<class-string, array<string>>
*/
private const CHAOTIC_CLASS_METHOD_NAMES = [
NodeVisitor::class => ['enterNode', 'leaveNode', 'beforeTraverse', 'afterTraverse'],
'PhpParser\NodeVisitor' => ['enterNode', 'leaveNode', 'beforeTraverse', 'afterTraverse'],
];
/**
@ -41,9 +44,14 @@ final class ClassMethodReturnTypeOverrideGuard
private $nodeTypeResolver;
/**
* @var NodeRepository
* @var ReflectionProvider
*/
private $nodeRepository;
private $reflectionProvider;
/**
* @var FamilyRelationsAnalyzer
*/
private $familyRelationsAnalyzer;
/**
* @var BetterNodeFinder
@ -53,19 +61,21 @@ final class ClassMethodReturnTypeOverrideGuard
public function __construct(
NodeNameResolver $nodeNameResolver,
NodeTypeResolver $nodeTypeResolver,
NodeRepository $nodeRepository,
ReflectionProvider $reflectionProvider,
FamilyRelationsAnalyzer $familyRelationsAnalyzer,
BetterNodeFinder $betterNodeFinder
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nodeRepository = $nodeRepository;
$this->reflectionProvider = $reflectionProvider;
$this->familyRelationsAnalyzer = $familyRelationsAnalyzer;
$this->betterNodeFinder = $betterNodeFinder;
}
public function shouldSkipClassMethod(ClassMethod $classMethod): bool
{
// 1. skip magic methods
if ($this->nodeNameResolver->isName($classMethod->name, '__*')) {
if ($classMethod->isMagic()) {
return true;
}
@ -74,24 +84,22 @@ final class ClassMethodReturnTypeOverrideGuard
return true;
}
// 3. skip has children and current has no return
$class = $classMethod->getAttribute(AttributeKey::PARENT_NODE);
$hasChildren = $class instanceof Class_ && $this->nodeRepository->hasClassChildren($class);
if (! $hasChildren) {
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
$hasReturn = (bool) $this->betterNodeFinder->findFirst(
(array) $classMethod->stmts,
function (Node $node): bool {
if (! $node instanceof Return_) {
return false;
}
return $node->expr instanceof Expr;
}
);
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
throw new ShouldNotHappenException();
}
if ($hasReturn) {
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
if ($childrenClassReflections === []) {
return false;
}
if ($this->hasClassMethodExprReturn($classMethod)) {
return false;
}
@ -119,15 +127,27 @@ final class ClassMethodReturnTypeOverrideGuard
return false;
}
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
foreach (self::CHAOTIC_CLASS_METHOD_NAMES as $chaoticClass => $chaoticMethodNames) {
if (! is_a($className, $chaoticClass, true)) {
if (! $this->reflectionProvider->hasClass($chaoticClass)) {
continue;
}
return in_array($methodName, $chaoticMethodNames, true);
$chaoticClassReflection = $this->reflectionProvider->getClass($chaoticClass);
if (! $classReflection->isSubclassOf($chaoticClassReflection->getName())) {
continue;
}
return $this->nodeNameResolver->isNames($classMethod, $chaoticMethodNames);
}
return false;
@ -172,4 +192,15 @@ final class ClassMethodReturnTypeOverrideGuard
return $isMatchingClassTypes;
}
private function hasClassMethodExprReturn(ClassMethod $classMethod): bool
{
return (bool) $this->betterNodeFinder->findFirst($classMethod->getStmts(), function (Node $node): bool {
if (! $node instanceof Return_) {
return false;
}
return $node->expr instanceof Expr;
});
}
}

View File

@ -4,60 +4,60 @@ declare(strict_types=1);
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 PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionVariantWithPhpDocs;
use PHPStan\Type\MixedType;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ClassMethodReturnVendorLockResolver extends AbstractNodeVendorLockResolver
{
public function isVendorLocked(ClassMethod $classMethod): bool
{
$classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if (! $classNode instanceof ClassLike) {
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classNode)) {
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classReflection)) {
return false;
}
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
/** @var string|null $parentClassName */
$parentClassName = $classMethod->getAttribute(AttributeKey::PARENT_CLASS_NAME);
if ($parentClassName !== null) {
return $this->isVendorLockedByParentClass($parentClassName, $methodName);
if ($this->isVendorLockedByParentClass($classReflection, $methodName)) {
return true;
}
$classNode = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($classNode instanceof Class_) {
return $this->isMethodVendorLockedByInterface($classNode, $methodName);
if ($classReflection->isTrait()) {
return false;
}
if ($classNode instanceof Interface_) {
return $this->isMethodVendorLockedByInterface($classNode, $methodName);
}
return false;
return $this->isMethodVendorLockedByInterface($classReflection, $methodName);
}
private function isVendorLockedByParentClass(string $parentClassName, string $methodName): bool
private function isVendorLockedByParentClass(ClassReflection $classReflection, string $methodName): bool
{
$parentClass = $this->nodeRepository->findClass($parentClassName);
if ($parentClass !== null) {
$parentClassMethod = $parentClass->getMethod($methodName);
// validate type is conflicting
// parent class method in local scope → it's ok
if ($parentClassMethod !== null) {
return $parentClassMethod->returnType !== null;
foreach ($classReflection->getParents() as $parentClassReflections) {
if (! $parentClassReflections->hasMethod($methodName)) {
continue;
}
// if not, look for it's parent parent
$parentClassMethodReflection = $parentClassReflections->getNativeMethod($methodName);
$parametersAcceptor = $parentClassMethodReflection->getVariants()[0];
if (! $parametersAcceptor instanceof FunctionVariantWithPhpDocs) {
continue;
}
// here we count only on strict types, not on docs
return ! $parametersAcceptor->getNativeReturnType() instanceof MixedType;
}
// validate type is conflicting
// parent class method in external scope → it's not ok
return method_exists($parentClassName, $methodName);
return false;
}
}

View File

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Php\PhpMethodReflection;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionClass;
final class ClassMethodVendorLockResolver extends AbstractNodeVendorLockResolver
{
@ -22,45 +22,33 @@ final class ClassMethodVendorLockResolver extends AbstractNodeVendorLockResolver
*/
public function isRemovalVendorLocked(ClassMethod $classMethod): bool
{
/** @var string $classMethodName */
$classMethodName = $this->nodeNameResolver->getName($classMethod);
/** @var Class_|Interface_|null $classLike */
$classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike === null) {
/** @var Scope $scope */
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
if ($this->isMethodVendorLockedByInterface($classLike, $classMethodName)) {
if ($this->isMethodVendorLockedByInterface($classReflection, $classMethodName)) {
return true;
}
if ($classLike->extends === null) {
return false;
}
/** @var string $className */
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
/** @var string[] $classParents */
$classParents = (array) class_parents($className);
foreach ($classParents as $classParent) {
if (! class_exists($classParent)) {
continue;
}
$parentClassReflection = new ReflectionClass($classParent);
foreach ($classReflection->getParents() as $parentClassReflection) {
if (! $parentClassReflection->hasMethod($classMethodName)) {
continue;
}
$methodReflection = $parentClassReflection->getMethod($classMethodName);
if (! $methodReflection->isAbstract()) {
$methodReflection = $parentClassReflection->getNativeMethod($classMethodName);
if (! $methodReflection instanceof PhpMethodReflection) {
continue;
}
return true;
if ($methodReflection->isAbstract()) {
return true;
}
}
return false;

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Privatization\VisibilityGuard\ClassMethodVisibilityGuard;
@ -25,64 +27,21 @@ final class ClassMethodVisibilityVendorLockResolver extends AbstractNodeVendorLo
*/
public function isParentLockedMethod(ClassMethod $classMethod): bool
{
/** @var string $className */
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
/** @var Scope $scope */
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if ($this->isInterfaceMethod($classMethod, $className)) {
return true;
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
return $this->hasParentMethod($className, $methodName);
}
/** @var ClassReflection[] $parentClassReflections */
$parentClassReflections = array_merge($classReflection->getParents(), $classReflection->getInterfaces());
public function isChildLockedMethod(ClassMethod $classMethod): bool
{
/** @var string $className */
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
return $this->hasChildMethod($className, $methodName);
}
private function isInterfaceMethod(ClassMethod $classMethod, string $className): bool
{
$interfaceMethodNames = $this->getInterfaceMethodNames($className);
return $this->nodeNameResolver->isNames($classMethod, $interfaceMethodNames);
}
private function hasParentMethod(string $className, string $methodName): bool
{
/** @var string[] $parentClasses */
$parentClasses = (array) class_parents($className);
foreach ($parentClasses as $parentClass) {
if (! method_exists($parentClass, $methodName)) {
continue;
}
return true;
}
return false;
}
private function hasChildMethod(string $desiredClassName, string $methodName): bool
{
foreach (get_declared_classes() as $className) {
if ($className === $desiredClassName) {
continue;
}
if (! is_a($className, $desiredClassName, true)) {
continue;
}
if (method_exists($className, $methodName)) {
foreach ($parentClassReflections as $parentClassReflection) {
if ($parentClassReflection->hasMethod($methodName)) {
return true;
}
}
@ -90,20 +49,25 @@ final class ClassMethodVisibilityVendorLockResolver extends AbstractNodeVendorLo
return false;
}
/**
* @return string[]
*/
private function getInterfaceMethodNames(string $className): array
public function isChildLockedMethod(ClassMethod $classMethod): bool
{
/** @var string[] $interfaces */
$interfaces = (array) class_implements($className);
/** @var Scope $scope */
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
$interfaceMethods = [];
foreach ($interfaces as $interface) {
$currentInterfaceMethods = get_class_methods($interface);
$interfaceMethods = array_merge($interfaceMethods, $currentInterfaceMethods);
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
return $interfaceMethods;
$methodName = $this->nodeNameResolver->getName($classMethod);
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
foreach ($childrenClassReflections as $childClassReflection) {
if ($childClassReflection->hasMethod($methodName)) {
return true;
}
}
return false;
}
}

View File

@ -4,94 +4,79 @@ 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 PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionProperty;
final class PropertyTypeVendorLockResolver extends AbstractNodeVendorLockResolver
{
public function isVendorLocked(Property $property): bool
{
$classLike = $property->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
/** @var Scope $scope */
$scope = $property->getAttribute(AttributeKey::SCOPE);
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
/** @var Class_|Interface_ $classLike */
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classLike)) {
if (! $this->hasParentClassChildrenClassesOrImplementsInterface($classReflection)) {
return false;
}
/** @var string $propertyName */
$propertyName = $this->nodeNameResolver->getName($property);
if ($this->isParentClassLocked($classLike, $propertyName)) {
if ($this->isParentClassLocked($classReflection, $propertyName)) {
return true;
}
return $this->isChildClassLocked($property, $classLike, $propertyName);
return $this->isChildClassLocked($property, $classReflection, $propertyName);
}
/**
* @param Class_|Interface_ $classLike
*/
private function isParentClassLocked(ClassLike $classLike, string $propertyName): bool
private function isParentClassLocked(ClassReflection $classReflection, 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
foreach ($classReflection->getParents() as $parentClassReflection) {
if ($parentClassReflection->hasProperty($propertyName)) {
// validate type is conflicting
// parent class property in external scope → it's not ok
return true;
}
}
return false;
}
/**
* @param Class_|Interface_ $classLike
*/
private function isChildClassLocked(Property $property, ClassLike $classLike, string $propertyName): bool
{
if (! $classLike instanceof Class_) {
private function isChildClassLocked(
Property $property,
ClassReflection $classReflection,
string $propertyName
): bool {
if (! $classReflection->isClass()) {
return false;
}
// is child class locker
// is child class locked?
if ($property->isPrivate()) {
return false;
}
$childrenClassNames = $this->getChildrenClassesByClass($classLike);
$childClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
foreach ($childrenClassNames as $childClassName) {
if (! property_exists($childClassName, $propertyName)) {
foreach ($childClassReflections as $childClassReflection) {
if (! $childClassReflection->hasProperty($propertyName)) {
continue;
}
$propertyReflection = $childClassReflection->getNativeProperty($propertyName);
// ensure the property is not in the parent class
$reflectionProperty = new ReflectionProperty($childClassName, $propertyName);
if ($reflectionProperty->class !== $childClassName) {
continue;
$propertyReflectionDeclaringClass = $propertyReflection->getDeclaringClass();
if ($propertyReflectionDeclaringClass->getName() === $childClassReflection->getName()) {
return true;
}
return true;
}
return false;

View File

@ -4,7 +4,10 @@ declare(strict_types=1);
namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class PropertyVisibilityVendorLockResolver extends AbstractNodeVendorLockResolver
@ -18,58 +21,52 @@ final class PropertyVisibilityVendorLockResolver extends AbstractNodeVendorLockR
*/
public function isParentLockedProperty(Property $property): bool
{
/** @var string $className */
$className = $property->getAttribute(AttributeKey::CLASS_NAME);
/** @var string $propertyName */
$propertyName = $this->nodeNameResolver->getName($property);
return $this->hasParentProperty($className, $propertyName);
}
public function isChildLockedProperty(Property $property): bool
{
/** @var string $className */
$className = $property->getAttribute(AttributeKey::CLASS_NAME);
/** @var string $propertyName */
$propertyName = $this->nodeNameResolver->getName($property);
return $this->hasChildProperty($className, $propertyName);
}
private function hasParentProperty(string $className, string $propertyName): bool
{
/** @var string[] $parentClasses */
$parentClasses = (array) class_parents($className);
foreach ($parentClasses as $parentClass) {
if (! property_exists($parentClass, $propertyName)) {
continue;
}
return true;
$classReflection = $this->resolveClassReflection($property);
if (! $classReflection instanceof ClassReflection) {
return false;
}
return false;
}
$propertyName = $this->nodeNameResolver->getName($property);
private function hasChildProperty(string $desiredClassName, string $propertyName): bool
{
foreach (get_declared_classes() as $className) {
if ($className === $desiredClassName) {
continue;
}
if (! is_a($className, $desiredClassName, true)) {
continue;
}
if (property_exists($className, $propertyName)) {
foreach ($classReflection->getParents() as $parentClassReflection) {
if ($parentClassReflection->hasProperty($propertyName)) {
return true;
}
}
return false;
}
public function isChildLockedProperty(Property $property): bool
{
$classReflection = $this->resolveClassReflection($property);
if (! $classReflection instanceof ClassReflection) {
return false;
}
$propertyName = $this->nodeNameResolver->getName($property);
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
foreach ($childrenClassReflections as $childClassReflection) {
if ($childClassReflection === $classReflection) {
continue;
}
if ($childClassReflection->hasProperty($propertyName)) {
return true;
}
}
return false;
}
private function resolveClassReflection(Node $node): ?ClassReflection
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
return $scope->getClassReflection();
}
}

View File

@ -2,6 +2,9 @@
parameters:
inferPrivatePropertyTypeFromConstructor: true
scanDirectories:
- stubs
includes:
- utils/phpstan-extensions/config/phpstan-extensions.neon
- vendor/phpstan/phpstan-nette/extension.neon

View File

@ -121,10 +121,11 @@ parameters:
- '#Cognitive complexity for "Rector\\Php80\\NodeResolver\\SwitchExprsResolver\:\:resolve\(\)" is \d+, keep it under 9#'
-
message: "#^Cognitive complexity for \"Rector\\\\PhpSpecToPHPUnit\\\\Rector\\\\MethodCall\\\\PhpSpecPromisesToPHPUnitAssertRector\\:\\:refactor\\(\\)\" is 13, keep it under 9$#"
count: 1
path: rules/php-spec-to-phpunit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php
-
message: '#Class cognitive complexity is \d+, keep it under \d+#'
paths:
@ -133,6 +134,7 @@ parameters:
- packages/node-type-resolver/src/NodeTypeResolver.php
- rules/code-quality-strict/src/Rector/Variable/MoveVariableDeclarationNearReferenceRector.php
- rules/php80/src/Rector/If_/NullsafeOperatorRector.php
- "#^Cognitive complexity for \"Rector\\\\Php70\\\\EregToPcreTransformer\\:\\:(.*?)\" is (.*?), keep it under 9$#"
- '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\If_\\SimplifyIfIssetToNullCoalescingRector\:\:shouldSkip\(\)" is 10, keep it under 9#'
- '#Cognitive complexity for "Rector\\TypeDeclaration\\PHPStan\\Type\\ObjectTypeSpecifier\:\:matchShortenedObjectType\(\)" is 10, keep it under 9#'
@ -158,8 +160,6 @@ parameters:
message: '#Class "Rector\\RectorGenerator\\Rector\\Closure\\AddNewServiceToSymfonyPhpConfigRector" is missing @see annotation with test case class reference#'
path: 'packages/rector-generator/src/Rector/Closure/AddNewServiceToSymfonyPhpConfigRector.php'
- '#Class Rector\\Renaming\\Tests\\Rector\\MethodCall\\RenameMethodRector\\Fixture\\SkipSelfMethodRename not found#'
- '#Parameter \#1 \$variable of class Rector\\Php70\\ValueObject\\VariableAssignPair constructor expects PhpParser\\Node\\Expr\\ArrayDimFetch\|PhpParser\\Node\\Expr\\PropertyFetch\|PhpParser\\Node\\Expr\\StaticPropertyFetch\|PhpParser\\Node\\Expr\\Variable, PhpParser\\Node\\Expr given#'
# is nested expr
@ -319,8 +319,6 @@ parameters:
###################################################
- '#should return ReflectionClassConstant\|null but returns ReflectionClassConstant\|false#'
- '#Method "evaluateBinaryToVersionCompareCondition\(\)" returns bool type, so the name should start with is/has/was#'
# soo many false positive naming - fix later with Recto rule
@ -404,12 +402,6 @@ parameters:
- rules/naming/src/Guard/PropertyConflictingNameGuard/AbstractPropertyConflictingNameGuard.php
- rules/naming/src/PropertyRenamer/AbstractPropertyRenamer.php
-
message: '#".*" regex need to use string named capture group instead of numeric#'
paths:
- packages/better-php-doc-parser/src/PhpDocParser/BetterPhpDocParser.php #268
- rules/php70/src/EregToPcreTransformer.php #277
-
message: '#There should be no empty class#'
paths:
@ -512,7 +504,6 @@ parameters:
# known values
- '#Method Rector\\Testing\\Finder\\RectorsFinder\:\:findClassesInDirectoriesByName\(\) should return array<class\-string\> but returns array<int, \(int\|string\)\>#'
- '#Property PhpParser\\Node\\Param\:\:\$type \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#'
- '#Parameter \#1 \$interfaces of method Rector\\PHPStanStaticTypeMapper\\TypeAnalyzer\\UnionTypeCommonTypeNarrower\:\:filterOutNativeInterfaces\(\) expects array<class\-string\>, array<string, string\> given#'
- '#Content of method "getFunctionLikePhpDocInfo\(\)" is duplicated with method "getFunctionLikePhpDocInfo\(\)" in "Rector\\TypeDeclaration\\TypeInferer\\ParamTypeInferer\\PHPUnitDataProviderParamTypeInferer" class\. Use unique content or abstract service instead#'
-
@ -528,3 +519,81 @@ parameters:
- '#Property Rector\\Core\\PhpParser\\Node\\AssignAndBinaryMap\:\:\$binaryOpToAssignClasses \(array<class\-string<PhpParser\\Node\\Expr\\BinaryOp\>, class\-string<PhpParser\\Node\\Expr\\BinaryOp\>\>\) does not accept array#'
- '#Content of method "configure\(\)" is duplicated with method "configure\(\)" in "Rector\\RemovingStatic\\Rector\\Class_\\NewUniqueObjectToEntityFactoryRector" class\. Use unique content or abstract service instead#'
- '#Content of method "configure\(\)" is duplicated with method "configure\(\)" in "Rector\\RemovingStatic\\Rector\\Class_\\PassFactoryToUniqueObjectRector" class\. Use unique content or abstract service instead#'
-
message: '#Function "class_exists\(\)" cannot be used/left in the code#'
paths:
- utils/phpstan-type-mapper-checker/src/Finder/PHPStanTypeClassFinder.php
- packages/testing/src/Finder/RectorsFinder.php
- packages/better-php-doc-parser/src/AnnotationReader/AnnotationReaderFactory.php
-
message: '#Function "property_exists\(\)" cannot be used/left in the code#'
paths:
# on PhpParser Nodes
- src/NodeManipulator/ClassMethodAssignManipulator.php
- rules/php80/src/Rector/If_/NullsafeOperatorRector.php
- packages/node-type-resolver/src/NodeVisitor/FunctionMethodAndClassNodeVisitor.php
- packages/node-name-resolver/src/NodeNameResolver.php
- packages/node-type-resolver/src/PHPStan/Scope/PHPStanNodeScopeResolver.php
- packages/node-name-resolver/src/NodeNameResolver/ClassNameResolver.php
- packages/node-type-resolver/src/NodeVisitor/StatementNodeVisitor.php
- packages/better-php-doc-parser/src/Printer/PhpDocInfoPrinter.php
- packages/better-php-doc-parser/src/Printer/MultilineSpaceFormatPreserver.php
-
message: '#Function "class_implements\(\)" cannot be used/left in the code#'
paths:
- rules/symfony/src/ValueObject/ServiceMap/ServiceMap.php
-
message: '#Instead of "ReflectionClass" class/interface use "PHPStan\\Reflection\\ClassReflection"#'
paths:
# own classes
- packages/rector-generator/src/Provider/SetsListProvider.php
- packages/testing/src/Finder/RectorsFinder.php
- packages/static-type-mapper/src/TypeFactory/UnionTypeFactory.php
- packages/set/src/RectorSetProvider.php
- packages/rector-generator/src/Provider/NodeTypesProvider.php
- '#Method Rector\\TypeDeclaration\\PhpParserTypeAnalyzer\:\:unwrapNullableAndToString\(\) should return string but returns string\|null#'
# @todo resolve in next PR!!! (Feb 2021)
- '#Function "array_filter\(\)" cannot be used/left in the code#'
- '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\Isset_\\IssetOnPropertyObjectToPropertyExistsRector\:\:refactor\(\)" is 11, keep it under 9#'
- '#Cognitive complexity for "Rector\\DeadCode\\UnusedNodeResolver\\ClassUnusedPrivateClassMethodResolver\:\:filterOutParentAbstractMethods\(\)" is 10, keep it under 9#'
# known types
- '#Call to an undefined method PHPStan\\Type\\ConstantType\:\:getValue\(\)#'
- '#Method Rector\\BetterPhpDocParser\\AnnotationReader\\NodeAnnotationReader\:\:getNativePropertyReflection\(\) should return ReflectionProperty\|null but return statement is missing#'
- '#Parameter \#1 \$node of method Rector\\NetteKdyby\\Naming\\VariableNaming\:\:resolveFromMethodCall\(\) expects PhpParser\\Node\\Expr\\MethodCall\|PhpParser\\Node\\Expr\\NullsafeMethodCall\|PhpParser\\Node\\Expr\\StaticCall, PhpParser\\Node given#'
- '#Parameter \#1 \$stmts of method Rector\\EarlyReturn\\Rector\\Return_\\PreparedValueToEarlyReturnRector\:\:collectIfs\(\) expects array<PhpParser\\Node\\Stmt\\If_\>, array<PhpParser\\Node\\Stmt\> given#'
- '#Access to an undefined property PhpParser\\Node\\FunctionLike\|PhpParser\\Node\\Stmt\\If_\:\:\$stmts#'
- '#Method Rector\\NetteKdyby\\DataProvider\\EventAndListenerTreeProvider\:\:getListeningClassMethodsInEventSubscriberByClass\(\) should return array<class\-string, array<PhpParser\\Node\\Stmt\\ClassMethod\>\> but returns array<string, array<PhpParser\\Node\\Stmt\\ClassMethod\>\>#'
-
message: '#Function "get_declared_classes\(\)" cannot be used/left in the code#'
paths:
- rules/restoration/src/ClassMap/ExistingClassesProvider.php
# false positives
-
message: '#Empty array passed to foreach#'
paths:
- rules/transform/src/Rector/New_/NewToConstructorInjectionRector.php
-
message: '#Parameter \#1 \$types of method Rector\\Defluent\\NodeAnalyzer\\FluentCallStaticTypeResolver\:\:filterOutAlreadyPresentParentClasses\(\) expects array<class\-string\>, array<int, string\> given#'
paths:
- rules/defluent/src/NodeAnalyzer/FluentCallStaticTypeResolver.php
- '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\Isset_\\IssetOnPropertyObjectToPropertyExistsRector\:\:refactor\(\)" is \d+, keep it under 9#'
# needed for native reflection parameter reference
-
message: '#Instead of "ReflectionFunction" class/interface use "PHPStan\\Reflection\\FunctionReflection"#'
paths:
- packages/read-write/src/Guard/VariableToConstantGuard.php
- '#Cognitive complexity for "Rector\\NetteCodeQuality\\FormControlTypeResolver\\MagicNetteFactoryInterfaceFormControlTypeResolver\:\:resolve\(\)" is 11, keep it under 9#'
- '#Cognitive complexity for "Rector\\NetteCodeQuality\\FormControlTypeResolver\\MagicNetteFactoryInterfaceFormControlTypeResolver\:\:resolve\(\)" is 12, keep it under 9#'

View File

@ -8,8 +8,8 @@ use Rector\CodingStyle\Rector\String_\SplitStringClassConstantToClassConstFetchR
use Rector\Core\Configuration\Option;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersion;
use Rector\DeadCode\Rector\ClassConst\RemoveUnusedClassConstantRector;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Rector\Privatization\Rector\Property\PrivatizeLocalPropertyToPrivatePropertyRector;
use Rector\Restoration\Rector\ClassMethod\InferParamFromClassMethodReturnRector;
use Rector\Restoration\ValueObject\InferParamFromClassMethodReturn;
use Rector\Set\ValueObject\SetList;
@ -74,8 +74,10 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$parameters->set(Option::SKIP, [
StringClassNameToClassConstantRector::class,
SplitStringClassConstantToClassConstFetchRector::class,
// false positives on constants used in rector.php
RemoveUnusedClassConstantRector::class,
PrivatizeLocalPropertyToPrivatePropertyRector::class => [
__DIR__ . '/src/Rector/AbstractTemporaryRector.php',
],
// test paths
'*/Fixture/*',

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\CakePHP\Naming;
use Nette\Utils\Strings;
use PHPStan\Reflection\ReflectionProvider;
use Rector\CakePHP\ImplicitNameResolver;
/**
@ -35,9 +36,15 @@ final class CakePHPFullyQualifiedClassNameResolver
*/
private $implicitNameResolver;
public function __construct(ImplicitNameResolver $implicitNameResolver)
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ImplicitNameResolver $implicitNameResolver, ReflectionProvider $reflectionProvider)
{
$this->implicitNameResolver = $implicitNameResolver;
$this->reflectionProvider = $reflectionProvider;
}
/**
@ -63,10 +70,7 @@ final class CakePHPFullyQualifiedClassNameResolver
// B. is Cake native class?
$cakePhpVersion = 'Cake\\' . $pseudoNamespace . '\\' . $shortClass;
if (class_exists($cakePhpVersion)) {
return $cakePhpVersion;
}
if (interface_exists($cakePhpVersion)) {
if ($this->reflectionProvider->hasClass($cakePhpVersion)) {
return $cakePhpVersion;
}

View File

@ -89,28 +89,15 @@ CODE_SAMPLE
private function shouldSkipMethodCall(MethodCall $methodCall): bool
{
$methodCallVar = $methodCall->var;
$scope = $methodCallVar->getAttribute(Scope::class);
if ($scope === null) {
return true;
}
$type = $scope->getType($methodCallVar);
$variableType = $this->getStaticType($methodCall->var);
// From PropertyFetch → skip
if ($type instanceof ThisType) {
if ($variableType instanceof ThisType) {
return true;
}
// Is Boolean return → skip
$scope = $methodCall->getAttribute(Scope::class);
if ($scope === null) {
return true;
}
$type = $scope->getType($methodCall);
if ($type instanceof BooleanType) {
$methodCallReturnType = $this->getStaticType($methodCall);
if ($methodCallReturnType instanceof BooleanType) {
return true;
}

View File

@ -2,21 +2,15 @@
namespace Rector\CodeQualityStrict\Tests\Rector\If_\MoveOutMethodCallInsideIfConditionRector\Fixture;
class SkipIfConditionMethodCallReturnBool
{
public function run($arg)
{
$obj = new self();
use Rector\CodeQualityStrict\Tests\Rector\If_\MoveOutMethodCallInsideIfConditionRector\Source\ClassMethodWithCall;
if ($obj->condition($arg)) {
final class SkipIfConditionMethodCallReturnBool
{
public function run(ClassMethodWithCall $classMethodWithCall, $arg)
{
if ($classMethodWithCall->condition($arg)) {
}
}
public function condition($arg): bool
{
}
}
?>

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQualityStrict\Tests\Rector\If_\MoveOutMethodCallInsideIfConditionRector\Source;
final class ClassMethodWithCall
{
public function condition($arg): bool
{
return mt_rand(0, 1) ? true : false;
}
}

View File

@ -8,15 +8,15 @@ use PhpParser\Node\Expr;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\Value\ValueResolver;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class CallableClassMethodMatcher
@ -31,32 +31,32 @@ final class CallableClassMethodMatcher
*/
private $nodeTypeResolver;
/**
* @var NodeRepository
*/
private $nodeRepository;
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
ValueResolver $valueResolver,
NodeTypeResolver $nodeTypeResolver,
NodeRepository $nodeRepository,
NodeNameResolver $nodeNameResolver
NodeNameResolver $nodeNameResolver,
ReflectionProvider $reflectionProvider
) {
$this->valueResolver = $valueResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nodeRepository = $nodeRepository;
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
}
/**
* @param Variable|PropertyFetch $objectExpr
*/
public function match(Expr $objectExpr, String_ $string): ?ClassMethod
public function match(Expr $objectExpr, String_ $string): ?PhpMethodReflection
{
$methodName = $this->valueResolver->getValue($string);
if (! is_string($methodName)) {
@ -67,25 +67,29 @@ final class CallableClassMethodMatcher
$objectType = $this->popFirstObjectType($objectType);
if ($objectType instanceof ObjectType) {
$class = $this->nodeRepository->findClass($objectType->getClassName());
if (! $class instanceof Class_) {
if (! $this->reflectionProvider->hasClass($objectType->getClassName())) {
return null;
}
$classMethod = $class->getMethod($methodName);
$classReflection = $this->reflectionProvider->getClass($objectType->getClassName());
if (! $classReflection->hasMethod($methodName)) {
return null;
}
if (! $classMethod instanceof ClassMethod) {
$stringScope = $string->getAttribute(AttributeKey::SCOPE);
$methodReflection = $classReflection->getMethod($methodName, $stringScope);
if (! $methodReflection instanceof PhpMethodReflection) {
return null;
}
if ($this->nodeNameResolver->isName($objectExpr, 'this')) {
return $classMethod;
return $methodReflection;
}
// is public method of another service
if ($classMethod->isPublic()) {
return $classMethod;
if ($methodReflection->isPublic()) {
return $methodReflection;
}
}

View File

@ -11,21 +11,19 @@ use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use PHPStan\Reflection\FunctionVariantWithPhpDocs;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Type\MixedType;
use PHPStan\Type\VoidType;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\StaticTypeMapper\StaticTypeMapper;
final class AnonymousFunctionFactory
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var NodeFactory
*/
@ -36,40 +34,47 @@ final class AnonymousFunctionFactory
*/
private $nodeNameResolver;
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
public function __construct(
BetterNodeFinder $betterNodeFinder,
NodeFactory $nodeFactory,
NodeNameResolver $nodeNameResolver
NodeNameResolver $nodeNameResolver,
StaticTypeMapper $staticTypeMapper
) {
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeFactory = $nodeFactory;
$this->nodeNameResolver = $nodeNameResolver;
$this->staticTypeMapper = $staticTypeMapper;
}
/**
* @param Variable|PropertyFetch $node
*/
public function create(ClassMethod $classMethod, Node $node): Closure
public function create(PhpMethodReflection $phpMethodReflection, Node $node): Closure
{
/** @var Return_[] $classMethodReturns */
$classMethodReturns = $this->betterNodeFinder->findInstanceOf((array) $classMethod->stmts, Return_::class);
/** @var FunctionVariantWithPhpDocs $functionVariantWithPhpDoc */
$functionVariantWithPhpDoc = $phpMethodReflection->getVariants()[0];
$anonymousFunction = new Closure();
$newParams = $this->copyParams($classMethod->params);
$newParams = $this->createParams($functionVariantWithPhpDoc->getParameters());
$anonymousFunction->params = $newParams;
$innerMethodCall = new MethodCall($node, $classMethod->name);
$innerMethodCall = new MethodCall($node, $phpMethodReflection->getName());
$innerMethodCall->args = $this->nodeFactory->createArgsFromParams($newParams);
if ($classMethod->returnType !== null) {
$newReturnType = $classMethod->returnType;
$newReturnType->setAttribute(AttributeKey::ORIGINAL_NODE, null);
$anonymousFunction->returnType = $newReturnType;
if (! $functionVariantWithPhpDoc->getReturnType() instanceof MixedType) {
$returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$functionVariantWithPhpDoc->getReturnType()
);
$anonymousFunction->returnType = $returnType;
}
// does method return something?
if ($this->hasClassMethodReturn($classMethodReturns)) {
if (! $functionVariantWithPhpDoc->getReturnType() instanceof VoidType) {
$anonymousFunction->stmts[] = new Return_($innerMethodCall);
} else {
$anonymousFunction->stmts[] = new Expression($innerMethodCall);
@ -83,32 +88,23 @@ final class AnonymousFunctionFactory
}
/**
* @param Param[] $params
* @param ParameterReflection[] $parameterReflections
* @return Param[]
*/
private function copyParams(array $params): array
private function createParams(array $parameterReflections): array
{
$newParams = [];
foreach ($params as $param) {
$newParam = clone $param;
$newParam->setAttribute(AttributeKey::ORIGINAL_NODE, null);
$newParam->var->setAttribute(AttributeKey::ORIGINAL_NODE, null);
$newParams[] = $newParam;
}
$params = [];
foreach ($parameterReflections as $parameterReflection) {
$param = new Param(new Variable($parameterReflection->getName()));
return $newParams;
}
/**
* @param Return_[] $nodes
*/
private function hasClassMethodReturn(array $nodes): bool
{
foreach ($nodes as $node) {
if ($node->expr !== null) {
return true;
if (! $parameterReflection->getType() instanceof MixedType) {
$paramType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($parameterReflection->getType());
$param->type = $paramType;
}
$params[] = $param;
}
return false;
return $params;
}
}

View File

@ -14,6 +14,7 @@ use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodReferenceAnalyzer;
use Rector\NodeCollector\ValueObject\ArrayCallable;
@ -32,9 +33,17 @@ final class ArrayThisCallToThisMethodCallRector extends AbstractRector
*/
private $arrayCallableMethodReferenceAnalyzer;
public function __construct(ArrayCallableMethodReferenceAnalyzer $arrayCallableMethodReferenceAnalyzer)
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
ArrayCallableMethodReferenceAnalyzer $arrayCallableMethodReferenceAnalyzer,
ReflectionProvider $reflectionProvider
) {
$this->arrayCallableMethodReferenceAnalyzer = $arrayCallableMethodReferenceAnalyzer;
$this->reflectionProvider = $reflectionProvider;
}
public function getRuleDefinition(): RuleDefinition
@ -94,6 +103,7 @@ CODE_SAMPLE
if (! $arrayCallable instanceof ArrayCallable) {
return null;
}
if ($this->isAssignedToNetteMagicOnProperty($node)) {
return null;
}
@ -107,12 +117,18 @@ CODE_SAMPLE
return null;
}
if (! $arrayCallable->isExistingMethod()) {
if (! $this->reflectionProvider->hasClass($arrayCallable->getClass())) {
return null;
}
$reflectionMethod = $arrayCallable->getReflectionMethod();
$classReflection = $this->reflectionProvider->getClass($arrayCallable->getClass());
if (! $classReflection->hasMethod($arrayCallable->getMethod())) {
return null;
}
$nativeReflectionClass = $classReflection->getNativeReflection();
$reflectionMethod = $nativeReflectionClass->getMethod($arrayCallable->getMethod());
$this->privatizeClassMethod($reflectionMethod);
if ($reflectionMethod->getNumberOfParameters() > 0) {

View File

@ -11,9 +11,8 @@ use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\Php\PhpMethodReflection;
use Rector\CodeQuality\NodeAnalyzer\CallableClassMethodMatcher;
use Rector\CodeQuality\NodeFactory\AnonymousFunctionFactory;
use Rector\Core\Rector\AbstractRector;
@ -133,12 +132,12 @@ CODE_SAMPLE
return null;
}
$classMethod = $this->callableClassMethodMatcher->match($objectVariable, $methodName);
if (! $classMethod instanceof ClassMethod) {
$phpMethodReflection = $this->callableClassMethodMatcher->match($objectVariable, $methodName);
if (! $phpMethodReflection instanceof PhpMethodReflection) {
return null;
}
return $this->anonymousFunctionFactory->create($classMethod, $objectVariable);
return $this->anonymousFunctionFactory->create($phpMethodReflection, $objectVariable);
}
private function shouldSkipArray(Array_ $array): bool

View File

@ -6,6 +6,8 @@ namespace Rector\CodeQuality\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Type;
use Rector\CodeQuality\NodeAnalyzer\ClassLikeAnalyzer;
use Rector\CodeQuality\NodeAnalyzer\LocalPropertyAnalyzer;
@ -38,14 +40,21 @@ final class CompleteDynamicPropertiesRector extends AbstractRector
*/
private $classLikeAnalyzer;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
MissingPropertiesFactory $missingPropertiesFactory,
LocalPropertyAnalyzer $localPropertyAnalyzer,
ClassLikeAnalyzer $classLikeAnalyzer
ClassLikeAnalyzer $classLikeAnalyzer,
ReflectionProvider $reflectionProvider
) {
$this->missingPropertiesFactory = $missingPropertiesFactory;
$this->localPropertyAnalyzer = $localPropertyAnalyzer;
$this->classLikeAnalyzer = $classLikeAnalyzer;
$this->reflectionProvider = $reflectionProvider;
}
public function getRuleDefinition(): RuleDefinition
@ -96,6 +105,17 @@ CODE_SAMPLE
return null;
}
$className = $this->getName($node);
if ($className === null) {
return null;
}
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}
$classReflection = $this->reflectionProvider->getClass($className);
// special case for Laravel Collection macro magic
$fetchedLocalPropertyNameToTypes = $this->localPropertyAnalyzer->resolveFetchedPropertiesToTypesFromClass(
$node
@ -106,10 +126,7 @@ CODE_SAMPLE
return null;
}
/** @var string $className */
$className = $this->getName($node);
$propertiesToComplete = $this->filterOutExistingProperties($className, $propertiesToComplete);
$propertiesToComplete = $this->filterOutExistingProperties($classReflection, $propertiesToComplete);
$newProperties = $this->missingPropertiesFactory->create(
$fetchedLocalPropertyNameToTypes,
@ -127,17 +144,23 @@ CODE_SAMPLE
return true;
}
$className = $this->getName($class);
$className = $this->nodeNameResolver->getName($class);
if ($className === null) {
return true;
}
// properties are accessed via magic, nothing we can do
if (method_exists($className, '__set')) {
if (! $this->reflectionProvider->hasClass($className)) {
return true;
}
return method_exists($className, '__get');
$classReflection = $this->reflectionProvider->getClass($className);
// properties are accessed via magic, nothing we can do
if ($classReflection->hasMethod('__set')) {
return true;
}
return $classReflection->hasMethod('__get');
}
/**
@ -158,14 +181,13 @@ CODE_SAMPLE
* @param string[] $propertiesToComplete
* @return string[]
*/
private function filterOutExistingProperties(string $className, array $propertiesToComplete): array
private function filterOutExistingProperties(ClassReflection $classReflection, array $propertiesToComplete): array
{
$missingPropertyNames = [];
// remove other properties that are accessible from this scope
foreach ($propertiesToComplete as $propertyToComplete) {
/** @var string $propertyToComplete */
if (property_exists($className, $propertyToComplete)) {
if ($classReflection->hasProperty($propertyToComplete)) {
continue;
}

View File

@ -81,7 +81,7 @@ CODE_SAMPLE
}
$valueArgument = $node->args[1]->value;
if (! $this->isStaticType($valueArgument, StringType::class)) {
if (! $this->nodeTypeResolver->isStaticType($valueArgument, StringType::class)) {
return null;
}

View File

@ -78,11 +78,11 @@ CODE_SAMPLE
if ($node->expr instanceof Identical) {
$identical = $node->expr;
if (! $this->isStaticType($identical->left, BooleanType::class)) {
if (! $this->nodeTypeResolver->isStaticType($identical->left, BooleanType::class)) {
return null;
}
if (! $this->isStaticType($identical->right, BooleanType::class)) {
if (! $this->nodeTypeResolver->isStaticType($identical->right, BooleanType::class)) {
return null;
}
@ -94,11 +94,11 @@ CODE_SAMPLE
private function processIdentical(Identical $identical): ?NotIdentical
{
if (! $this->isStaticType($identical->left, BooleanType::class)) {
if (! $this->nodeTypeResolver->isStaticType($identical->left, BooleanType::class)) {
return null;
}
if (! $this->isStaticType($identical->right, BooleanType::class)) {
if (! $this->nodeTypeResolver->isStaticType($identical->right, BooleanType::class)) {
return null;
}

View File

@ -184,9 +184,9 @@ CODE_SAMPLE
return null;
}
$tagValueNode = $phpDocInfo->getVarTagValueNode();
if ($tagValueNode instanceof VarTagValueNode) {
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $tagValueNode);
$varTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($varTagValueNode instanceof VarTagValueNode) {
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $varTagValueNode);
}
return new BooleanNot(new Instanceof_($expr, new FullyQualified($type->getClassName())));

View File

@ -64,12 +64,13 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if ($this->isStaticType($node->left, BooleanType::class) && ! $this->valueResolver->isTrueOrFalse(
$node->left
)) {
if ($this->nodeTypeResolver->isStaticType(
$node->left,
BooleanType::class
) && ! $this->valueResolver->isTrueOrFalse($node->left)) {
return $this->processBoolTypeToNotBool($node, $node->left, $node->right);
}
if (! $this->isStaticType($node->right, BooleanType::class)) {
if (! $this->nodeTypeResolver->isStaticType($node->right, BooleanType::class)) {
return null;
}
if ($this->valueResolver->isTrueOrFalse($node->right)) {

View File

@ -144,11 +144,11 @@ CODE_SAMPLE
return $this->resolveString($isNegated, $expr);
}
if ($this->isStaticType($expr, IntegerType::class)) {
if ($this->nodeTypeResolver->isStaticType($expr, IntegerType::class)) {
return $this->resolveInteger($isNegated, $expr);
}
if ($this->isStaticType($expr, FloatType::class)) {
if ($this->nodeTypeResolver->isStaticType($expr, FloatType::class)) {
return $this->resolveFloat($isNegated, $expr);
}

View File

@ -13,6 +13,7 @@ use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
@ -24,6 +25,16 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
*/
final class IssetOnPropertyObjectToPropertyExistsRector extends AbstractRector
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change isset on property object to property_exists() and not null check', [
@ -86,15 +97,28 @@ CODE_SAMPLE
}
$propertyFetchVarType = $this->getObjectType($issetVar->var);
if ($propertyFetchVarType instanceof TypeWithClassName && property_exists(
$propertyFetchVarType->getClassName(),
$propertyFetchName
)) {
$newNodes[] = $this->createNotIdenticalToNull($issetVar);
continue;
}
if ($propertyFetchVarType instanceof TypeWithClassName) {
if (! $this->reflectionProvider->hasClass($propertyFetchVarType->getClassName())) {
continue;
}
$newNodes[] = $this->replaceToPropertyExistsWithNullCheck($issetVar->var, $propertyFetchName, $issetVar);
$classReflection = $this->reflectionProvider->getClass($propertyFetchVarType->getClassName());
if (! $classReflection->hasProperty($propertyFetchName)) {
$newNodes[] = $this->replaceToPropertyExistsWithNullCheck(
$issetVar->var,
$propertyFetchName,
$issetVar
);
} else {
$newNodes[] = $this->createNotIdenticalToNull($issetVar);
}
} else {
$newNodes[] = $this->replaceToPropertyExistsWithNullCheck(
$issetVar->var,
$propertyFetchName,
$issetVar
);
}
}
return $this->nodeFactory->createReturnBooleanAnd($newNodes);

View File

@ -61,7 +61,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (! $this->isStaticType($node->cond, BooleanType::class)) {
if (! $this->nodeTypeResolver->isStaticType($node->cond, BooleanType::class)) {
return null;
}

View File

@ -97,7 +97,7 @@ final class UnnecessaryTernaryExpressionRector extends AbstractRector
private function processNonBinaryCondition(Expr $ifExpression, Expr $elseExpression, Expr $condition): ?Node
{
if ($this->valueResolver->isTrue($ifExpression) && $this->valueResolver->isFalse($elseExpression)) {
if ($this->isStaticType($condition, BooleanType::class)) {
if ($this->nodeTypeResolver->isStaticType($condition, BooleanType::class)) {
return $condition;
}
@ -105,7 +105,7 @@ final class UnnecessaryTernaryExpressionRector extends AbstractRector
}
if ($this->valueResolver->isFalse($ifExpression) && $this->valueResolver->isTrue($elseExpression)) {
if ($this->isStaticType($condition, BooleanType::class)) {
if ($this->nodeTypeResolver->isStaticType($condition, BooleanType::class)) {
return new BooleanNot($condition);
}

View File

@ -2,6 +2,8 @@
namespace Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture;
use Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Source\SortingClass;
class AnotherClass
{
/**
@ -24,30 +26,14 @@ class AnotherClass
}
}
final class SortingClass
{
public function publicSort($a, $b)
{
return $a <=> $b;
}
protected function protectedSort($a, $b)
{
return $a <=> $b;
}
private function privateSort($a, $b)
{
return $a <=> $b;
}
}
?>
-----
<?php
namespace Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture;
use Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Source\SortingClass;
class AnotherClass
{
/**
@ -74,22 +60,4 @@ class AnotherClass
}
}
final class SortingClass
{
public function publicSort($a, $b)
{
return $a <=> $b;
}
protected function protectedSort($a, $b)
{
return $a <=> $b;
}
private function privateSort($a, $b)
{
return $a <=> $b;
}
}
?>

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Source;
final class SortingClass
{
public function publicSort($a, $b)
{
return $a <=> $b;
}
protected function protectedSort($a, $b)
{
return $a <=> $b;
}
private function privateSort($a, $b)
{
return $a <=> $b;
}
}

View File

@ -2,7 +2,7 @@
namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture;
class PropertyAfterInstantiationIfAlwaysExists
final class PropertyAfterInstantiationIfAlwaysExists
{
public $x;
public $y;
@ -22,7 +22,7 @@ class PropertyAfterInstantiationIfAlwaysExists
namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture;
class PropertyAfterInstantiationIfAlwaysExists
final class PropertyAfterInstantiationIfAlwaysExists
{
public $x;
public $y;

View File

@ -2,12 +2,11 @@
namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture;
class PropertyMaybeExistsAfterInstantiation
final class PropertyMaybeExistsAfterInstantiation
{
public function init()
{
$this->x = 'a';
$this->y = 'b';
}
public function run()
@ -16,8 +15,6 @@ class PropertyMaybeExistsAfterInstantiation
$obj->init();
isset($obj->x);
isset($obj->y);
isset($obj->x) && isset($obj->y);
}
}
@ -27,12 +24,11 @@ class PropertyMaybeExistsAfterInstantiation
namespace Rector\CodeQuality\Tests\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector\Fixture;
class PropertyMaybeExistsAfterInstantiation
final class PropertyMaybeExistsAfterInstantiation
{
public function init()
{
$this->x = 'a';
$this->y = 'b';
}
public function run()
@ -41,8 +37,6 @@ class PropertyMaybeExistsAfterInstantiation
$obj->init();
property_exists($obj, 'x') && $obj->x !== null;
property_exists($obj, 'y') && $obj->y !== null;
property_exists($obj, 'x') && $obj->x !== null && (property_exists($obj, 'y') && $obj->y !== null);
}
}

View File

@ -12,11 +12,11 @@ use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\UseUse;
use PHPStan\Reflection\ReflectionProvider;
use Rector\CodingStyle\ClassNameImport\AliasUsesResolver;
use Rector\CodingStyle\ClassNameImport\ClassNameImportSkipper;
use Rector\Core\Configuration\Option;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PostRector\Collector\UseNodesToAddCollector;
use Rector\PSR4\Collector\RenamedClassesCollector;
@ -66,6 +66,11 @@ final class NameImporter
*/
private $renamedClassesCollector;
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(
AliasUsesResolver $aliasUsesResolver,
ClassNameImportSkipper $classNameImportSkipper,
@ -73,7 +78,8 @@ final class NameImporter
ParameterProvider $parameterProvider,
RenamedClassesCollector $renamedClassesCollector,
StaticTypeMapper $staticTypeMapper,
UseNodesToAddCollector $useNodesToAddCollector
UseNodesToAddCollector $useNodesToAddCollector,
ReflectionProvider $reflectionProvider
) {
$this->staticTypeMapper = $staticTypeMapper;
$this->aliasUsesResolver = $aliasUsesResolver;
@ -82,6 +88,7 @@ final class NameImporter
$this->parameterProvider = $parameterProvider;
$this->useNodesToAddCollector = $useNodesToAddCollector;
$this->renamedClassesCollector = $renamedClassesCollector;
$this->reflectionProvider = $reflectionProvider;
}
public function importName(Name $name): ?Name
@ -198,15 +205,17 @@ final class NameImporter
if ($autoImportNames && ! $parentNode instanceof Node && ! Strings::contains(
$fullName,
'\\'
) && function_exists($fullName)) {
) && $this->reflectionProvider->hasFunction(new Name($fullName), null)) {
return true;
}
if ($parentNode instanceof ConstFetch) {
return count($name->parts) === 1;
}
if ($parentNode instanceof FuncCall) {
return count($name->parts) === 1;
}
return false;
}
@ -225,11 +234,12 @@ final class NameImporter
}
// skip-non existing class-likes and functions
if (ClassExistenceStaticHelper::doesClassLikeExist($classOrFunctionName)) {
if ($this->reflectionProvider->hasClass($classOrFunctionName)) {
return false;
}
return ! function_exists($classOrFunctionName);
$parent = $name->getAttribute(AttributeKey::PARENT_NODE);
return ! $parent instanceof FuncCall;
}
private function addUseImport(Name $name, FullyQualifiedObjectType $fullyQualifiedObjectType): void

View File

@ -92,12 +92,14 @@ CODE_SAMPLE
/** @var string $methodName */
$methodName = $this->getName($node->name);
foreach ($classReflection->getParentClassesNames() as $parentClassName) {
if (! method_exists($parentClassName, $methodName)) {
foreach ($classReflection->getParents() as $parentClassReflection) {
if (! $parentClassReflection->hasMethod($methodName)) {
continue;
}
$parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName);
$nativeClassReflection = $parentClassReflection->getNativeReflection();
$parentReflectionMethod = $nativeClassReflection->getMethod($methodName);
if ($this->isClassMethodCompatibleWithParentReflectionMethod($node, $parentReflectionMethod)) {
return null;
}

View File

@ -28,10 +28,10 @@ final class YieldClassMethodToArrayClassMethodRector extends AbstractRector impl
/**
* @var string
*/
public const METHODS_BY_TYPE = '$methodsByType';
public const METHODS_BY_TYPE = 'methods_by_type';
/**
* @var string[][]
* @var array<class-string, string[]>
*/
private $methodsByType = [];
@ -41,7 +41,7 @@ final class YieldClassMethodToArrayClassMethodRector extends AbstractRector impl
private $nodeTransformer;
/**
* @param string[][] $methodsByType
* @param array<class-string, string[]> $methodsByType
*/
public function __construct(NodeTransformer $nodeTransformer, array $methodsByType = [])
{

View File

@ -49,7 +49,7 @@ final class PreferThisOrSelfMethodCallRector extends AbstractRector implements C
private const SELF = 'self';
/**
* @var array<string, string>
* @var array<class-string, string>
*/
private $typeToPreference = [];
@ -79,7 +79,7 @@ CODE_SAMPLE
,
[
self::TYPE_TO_PREFERENCE => [
'\PHPUnit\Framework\TestCase' => self::PREFER_SELF,
'PHPUnit\Framework\TestCase' => self::PREFER_SELF,
],
]
),

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@ -18,6 +19,16 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
*/
final class SplitStringClassConstantToClassConstFetchRector extends AbstractRector
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
@ -78,7 +89,7 @@ CODE_SAMPLE
// a possible constant reference
[$possibleClass, $secondPart] = explode('::', $node->value);
if (! class_exists($possibleClass)) {
if (! $this->reflectionProvider->hasClass($possibleClass)) {
return null;
}

View File

@ -9,6 +9,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@ -24,6 +25,16 @@ final class UseClassKeywordForClassNameResolutionRector extends AbstractRector
*/
private const CLASS_BEFORE_STATIC_ACCESS_REGEX = '#(?<class_name>[\\\\a-zA-Z0-9_\\x80-\\xff]*)::#';
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
public function __construct(ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
@ -83,7 +94,7 @@ CODE_SAMPLE
$classNames = [];
foreach ($matches['class_name'] as $matchedClassName) {
if (! class_exists($matchedClassName)) {
if (! $this->reflectionProvider->hasClass($matchedClassName)) {
continue;
}
@ -119,7 +130,7 @@ CODE_SAMPLE
{
$exprsToConcat = [];
foreach ($parts as $part) {
if (class_exists($part)) {
if ($this->reflectionProvider->hasClass($part)) {
$exprsToConcat[] = new ClassConstFetch(new FullyQualified(ltrim($part, '\\')), 'class');
} else {
$exprsToConcat[] = new String_($part);

View File

@ -1,11 +0,0 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassConst\VarConstantCommentRector\Fixture;
class SkipClassString
{
/**
* @var class-string
*/
const HI = self::class;
}

View File

@ -2,15 +2,14 @@
namespace Rector\CodingStyle\Tests\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector\Fixture;
class FixtureFromMethodCall
final class FixtureFromMethodCall
{
public function run()
{
$self = new self();
count($self->getData()) === 0;
0 === count($self->getData());
count($self->getData()) > 0;
0 < count($self->getData());
count($this->getData()) === 0;
0 === count($this->getData());
count($this->getData()) > 0;
0 < count($this->getData());
}
public function getData(): array
@ -25,15 +24,14 @@ class FixtureFromMethodCall
namespace Rector\CodingStyle\Tests\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector\Fixture;
class FixtureFromMethodCall
final class FixtureFromMethodCall
{
public function run()
{
$self = new self();
$self->getData() === [];
[] === $self->getData();
$self->getData() !== [];
[] !== $self->getData();
$this->getData() === [];
[] === $this->getData();
$this->getData() !== [];
[] !== $this->getData();
}
public function getData(): array

View File

@ -23,16 +23,25 @@ final class ImportFullyQualifiedNamesRectorTest extends AbstractRectorTestCase
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideDataFunction(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureFunction');
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideDataGeneric(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureGeneric');

View File

@ -9,12 +9,12 @@ use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\DeadCode\Comparator\Parameter\ParameterDefaultsComparator;
use Rector\DeadCode\Comparator\Parameter\ParameterTypeComparator;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeCollector\Reflection\MethodReflectionProvider;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -31,11 +31,6 @@ final class CurrentAndParentClassMethodComparator
*/
private $nodeComparator;
/**
* @var NodeRepository
*/
private $nodeRepository;
/**
* @var MethodReflectionProvider
*/
@ -53,14 +48,12 @@ final class CurrentAndParentClassMethodComparator
public function __construct(
NodeNameResolver $nodeNameResolver,
NodeRepository $nodeRepository,
MethodReflectionProvider $methodReflectionProvider,
ParameterDefaultsComparator $parameterDefaultsComparator,
ParameterTypeComparator $parameterTypeComparator,
NodeComparator $nodeComparator
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->nodeRepository = $nodeRepository;
$this->methodReflectionProvider = $methodReflectionProvider;
$this->parameterDefaultsComparator = $parameterDefaultsComparator;
$this->parameterTypeComparator = $parameterTypeComparator;
@ -126,46 +119,54 @@ final class CurrentAndParentClassMethodComparator
ClassMethod $classMethod,
StaticCall $staticCall
): bool {
/** @var string $className */
$className = $staticCall->getAttribute(AttributeKey::CLASS_NAME);
$parentClassName = get_parent_class($className);
if (! $parentClassName) {
throw new ShouldNotHappenException();
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($staticCall->name);
if ($methodName === null) {
return false;
}
$parentClassMethod = $this->nodeRepository->findClassMethod($parentClassName, $methodName);
if (! $parentClassMethod instanceof ClassMethod) {
return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName);
foreach ($classReflection->getParents() as $parentClassReflection) {
if (! $parentClassReflection->hasMethod($methodName)) {
continue;
}
$nativeParentClassReflection = $parentClassReflection->getNativeReflection();
$nativeParentClassMethodReflection = $nativeParentClassReflection->getMethod($methodName);
if (! $nativeParentClassMethodReflection->isProtected()) {
return $this->checkOverrideUsingReflection($classMethod, $parentClassReflection, $methodName);
}
if (! $nativeParentClassMethodReflection->isPublic()) {
return $this->checkOverrideUsingReflection($classMethod, $parentClassReflection, $methodName);
}
return true;
}
if (! $parentClassMethod->isProtected()) {
return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName);
}
if (! $classMethod->isPublic()) {
return $this->checkOverrideUsingReflection($classMethod, $parentClassName, $methodName);
}
return true;
return false;
}
private function checkOverrideUsingReflection(
ClassMethod $classMethod,
string $parentClassName,
ClassReflection $classReflection,
string $methodName
): bool {
// @todo use phpstan reflecoitn
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
throw new ShouldNotHappenException();
}
$parentMethodReflection = $this->methodReflectionProvider->provideByClassAndMethodName(
$parentClassName,
$methodName,
$scope
);
$parentMethodReflection = $classReflection->getMethod($methodName, $scope);
// 3rd party code
if ($parentMethodReflection !== null) {

View File

@ -11,12 +11,15 @@ use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionFunction;
use ReflectionParameter;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
final class CallDefaultParamValuesResolver
{
@ -30,10 +33,26 @@ final class CallDefaultParamValuesResolver
*/
private $nodeNameResolver;
public function __construct(NodeRepository $nodeRepository, NodeNameResolver $nodeNameResolver)
{
/**
* @var ReflectionProvider
*/
private $reflectionProvider;
/**
* @var PrivatesAccessor
*/
private $privatesAccessor;
public function __construct(
NodeRepository $nodeRepository,
NodeNameResolver $nodeNameResolver,
ReflectionProvider $reflectionProvider,
PrivatesAccessor $privatesAccessor
) {
$this->nodeRepository = $nodeRepository;
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
$this->privatesAccessor = $privatesAccessor;
}
/**
@ -89,29 +108,39 @@ final class CallDefaultParamValuesResolver
*/
private function resolveFromFunctionName(string $functionName): array
{
$functionNode = $this->nodeRepository->findFunction($functionName);
if ($functionNode !== null) {
return $this->resolveFromFunctionLike($functionNode);
$function = $this->nodeRepository->findFunction($functionName);
if ($function instanceof Function_) {
return $this->resolveFromFunctionLike($function);
}
// non existing function
if (! function_exists($functionName)) {
$functionNameNode = new Name($functionName);
if (! $this->reflectionProvider->hasFunction($functionNameNode, null)) {
return [];
}
$reflectionFunction = new ReflectionFunction($functionName);
if ($reflectionFunction->isUserDefined()) {
$defaultValues = [];
foreach ($reflectionFunction->getParameters() as $key => $reflectionParameter) {
if ($reflectionParameter->isDefaultValueAvailable()) {
$defaultValues[$key] = BuilderHelpers::normalizeValue($reflectionParameter->getDefaultValue());
}
}
return $defaultValues;
$functionReflection = $this->reflectionProvider->getFunction($functionNameNode, null);
if ($functionReflection->isBuiltin()) {
return [];
}
return [];
$defaultValues = [];
$parametersAcceptor = $functionReflection->getVariants()[0];
foreach ($parametersAcceptor->getParameters() as $key => $reflectionParameter) {
/** @var ReflectionParameter $nativeReflectionParameter */
$nativeReflectionParameter = $this->privatesAccessor->getPrivateProperty(
$reflectionParameter,
'reflection'
);
if (! $nativeReflectionParameter->isDefaultValueAvailable()) {
continue;
}
$defaultValues[$key] = BuilderHelpers::normalizeValue($nativeReflectionParameter->getDefaultValue());
}
return $defaultValues;
}
}

View File

@ -7,6 +7,9 @@ namespace Rector\DeadCode\Rector\ClassConst;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\ShouldNotHappenException;
use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\ApiPhpDocTagNode;
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
use Rector\Core\NodeManipulator\ClassConstManipulator;
@ -74,12 +77,15 @@ CODE_SAMPLE
return null;
}
/** @var string|null $class */
$class = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($class === null) {
return null;
/** @var Scope $scope */
$scope = $node->getAttribute(AttributeKey::SCOPE);
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
throw new ShouldNotHappenException();
}
$nodeRepositoryFindInterface = $this->nodeRepository->findInterface($class);
$nodeRepositoryFindInterface = $this->nodeRepository->findInterface($classReflection->getName());
// 0. constants declared in interfaces have to be public
if ($nodeRepositoryFindInterface !== null) {
@ -90,13 +96,18 @@ CODE_SAMPLE
/** @var string $constant */
$constant = $this->getName($node);
$directUseClasses = $this->nodeRepository->findDirectClassConstantFetches($class, $constant);
if ($directUseClasses !== []) {
return null;
}
$directUsingClassReflections = $this->nodeRepository->findDirectClassConstantFetches(
$classReflection,
$constant
);
$indirectUseClasses = $this->nodeRepository->findIndirectClassConstantFetches($class, $constant);
if ($indirectUseClasses !== []) {
$indirectUsingClassReflections = $this->nodeRepository->findIndirectClassConstantFetches(
$classReflection,
$constant
);
$usingClassReflections = array_merge($directUsingClassReflections, $indirectUsingClassReflections);
if ($usingClassReflections !== []) {
return null;
}
@ -125,6 +136,7 @@ CODE_SAMPLE
}
$classLike = $classConst->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike instanceof ClassLike) {
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classLike);
return $phpDocInfo->hasByType(ApiPhpDocTagNode::class);

View File

@ -111,7 +111,7 @@ CODE_SAMPLE
return false;
}
if ($this->isName($classMethod, '__*')) {
if ($classMethod->isMagic()) {
return false;
}

Some files were not shown because too many files have changed in this diff Show More