[TypeDeclaration] Removing NodeRepository from ParamTypeDeclarationRector (#597)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2021-08-05 10:22:02 +02:00 committed by GitHub
parent 3eb503653f
commit ad15c5f694
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 155 additions and 281 deletions

View File

@ -6,7 +6,6 @@ namespace Rector\NodeCollector\NodeCollector;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeNameResolver\NodeNameResolver;
@ -58,15 +57,6 @@ final class NodeRepository
return $traits;
}
/**
* @return Class_[]|Interface_[]
* @deprecated Use static reflection instead
*/
public function findClassesAndInterfacesByType(string $type): array
{
return array_merge($this->findChildrenOfClass($type), $this->findImplementersOfInterface($type));
}
/**
* @deprecated Use static reflection instead
*
@ -89,16 +79,6 @@ final class NodeRepository
return $childrenClasses;
}
/**
* @deprecated Use static reflection instead
*
* @param class-string $class
*/
public function findInterface(string $class): ?Interface_
{
return $this->parsedNodeCollector->findInterface($class);
}
/**
* @deprecated Use static reflection instead
*
@ -132,24 +112,4 @@ final class NodeRepository
return $currentClassName !== $desiredClass;
}
/**
* @return Interface_[]
*/
private function findImplementersOfInterface(string $interface): array
{
$implementerInterfaces = [];
foreach ($this->parsedNodeCollector->getInterfaces() as $interfaceNode) {
$className = $interfaceNode->getAttribute(AttributeKey::CLASS_NAME);
if (! $this->isChildOrEqualClassLike($interface, $className)) {
continue;
}
$implementerInterfaces[] = $interfaceNode;
}
return $implementerInterfaces;
}
}

View File

@ -64,11 +64,6 @@ final class ParsedNodeCollector
return $this->classes[$name] ?? null;
}
public function findInterface(string $name): ?Interface_
{
return $this->interfaces[$name] ?? null;
}
public function findTrait(string $name): ?Trait_
{
return $this->traits[$name] ?? null;

View File

@ -6,14 +6,21 @@ namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Reflection\ReflectionClass;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\SmartFileSystem\Normalizer\PathNormalizer;
final class ClassMethodParamVendorLockResolver
{
public function __construct(
private NodeNameResolver $nodeNameResolver
private NodeNameResolver $nodeNameResolver,
private PathNormalizer $pathNormalizer,
private ReflectionProvider $reflectionProvider,
private PrivatesAccessor $privatesAccessor
) {
}
@ -23,14 +30,21 @@ final class ClassMethodParamVendorLockResolver
return true;
}
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
$classReflection = $this->resolveClassReflection($classMethod);
if (! $classReflection instanceof ClassReflection) {
return false;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
/** @var string $methodName */
$methodName = $this->nodeNameResolver->getName($classMethod);
if ($this->hasTraitMethodVendorLock($classReflection, $methodName)) {
return true;
}
// has interface vendor lock? → better skip it, as PHPStan has access only to just analyzed classes
if ($this->hasParentInterfaceMethod($classReflection, $methodName)) {
return true;
}
$methodName = $this->nodeNameResolver->getName($classMethod);
@ -41,7 +55,86 @@ final class ClassMethodParamVendorLockResolver
}
// parent type
if ($ancestorClassReflection->hasNativeMethod($methodName)) {
if (! $ancestorClassReflection->hasNativeMethod($methodName)) {
continue;
}
// is file in vendor?
$fileName = $ancestorClassReflection->getFileName();
// probably internal class
if ($fileName === false) {
continue;
}
$normalizedFileName = $this->pathNormalizer->normalizePath($fileName);
return str_contains($normalizedFileName, '/vendor/');
}
return false;
}
/**
* @return ReflectionClass[]
*/
private function findRelatedClassReflections(ClassReflection $classReflection): array
{
// @todo decouple to some reflection family finder?
/** @var ReflectionClass[] $reflectionClasses */
$reflectionClasses = $this->privatesAccessor->getPrivateProperty($this->reflectionProvider, 'classes');
$relatedClassReflections = [];
foreach ($reflectionClasses as $reflectionClass) {
if ($reflectionClass->getName() === $classReflection->getName()) {
continue;
}
// is related?
if (! $reflectionClass->isSubclassOf($classReflection->getName())) {
continue;
}
$relatedClassReflections[] = $reflectionClass;
}
return $relatedClassReflections;
}
private function hasTraitMethodVendorLock(ClassReflection $classReflection, string $methodName): bool
{
$relatedReflectionClasses = $this->findRelatedClassReflections($classReflection);
foreach ($relatedReflectionClasses as $relatedReflectionClass) {
foreach ($relatedReflectionClass->getTraits() as $traitReflectionClass) {
/** @var ClassReflection $traitReflectionClass */
if ($traitReflectionClass->hasMethod($methodName)) {
return true;
}
}
}
return false;
}
private function resolveClassReflection(ClassMethod $classMethod): ClassReflection|null
{
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
return $scope->getClassReflection();
}
/**
* Has interface even in our project?
* Better skip it, as PHPStan has access only to just analyzed classes.
* This might change type, that works for current class, but breaks another implementer.
*/
private function hasParentInterfaceMethod(ClassReflection $classReflection, string $methodName): bool
{
foreach ($classReflection->getInterfaces() as $interfaceClassReflection) {
if ($interfaceClassReflection->hasMethod($methodName)) {
return true;
}
}

View File

@ -2,7 +2,7 @@
namespace Rector\Tests\Php80\Rector\FunctionLike\UnionTypesRector\Fixture;
use Rector\Tests\Php80\Rector\FunctionLike\UnionTypesRector\Source\ParentClassWithMethod;
use Rector\Tests\Php80\Rector\FunctionLike\UnionTypesRector\Source\vendor\ParentClassWithMethod;
final class Child extends ParentClassWithMethod
{

View File

@ -2,9 +2,9 @@
declare(strict_types=1);
namespace Rector\Tests\Php80\Rector\FunctionLike\UnionTypesRector\Source;
namespace Rector\Tests\Php80\Rector\FunctionLike\UnionTypesRector\Source\vendor;
class ParentClassWithMethod
abstract class ParentClassWithMethod
{
public function enqueueAt($at)
{

View File

@ -2,7 +2,7 @@
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\AbstractParentClass;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\vendor\AbstractParentClass;
final class ChildClass extends AbstractParentClass
{
@ -27,7 +27,7 @@ final class ChildClass extends AbstractParentClass
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\AbstractParentClass;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\vendor\AbstractParentClass;
final class ChildClass extends AbstractParentClass
{

View File

@ -41,9 +41,6 @@ final class LocalChildClass extends AbstractLocalParentClass
{
}
/**
* @param int $number
*/
public function changeToo(int $number)
{
}

View File

@ -1,44 +0,0 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
interface SniffInterface
{
/**
* @param int $position
*/
public function process(string $file, $position);
}
final class CoolSniffWithLocalinterface implements SniffInterface
{
/**
* @param int $position
*/
public function process(string $file, $position)
{
}
}
?>
-----
<?php
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
interface SniffInterface
{
public function process(string $file, int $position);
}
final class CoolSniffWithLocalinterface implements SniffInterface
{
/**
* @param int $position
*/
public function process(string $file, int $position)
{
}
}
?>

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
trait SkipTraitWithInterfaceImplementation
{
public function getCount($items)
{
return 5;
}
}
interface SomeInterface
{
/**
* @param array $items
* @return int
*/
public function getCount($items);
}
class SomeClassWithInterfaceAndTrait implements SomeInterface
{
use SkipTraitWithInterfaceImplementation;
}

View File

@ -2,7 +2,7 @@
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\SniffInterface;
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\vendor\SniffInterface;
final class SkipVendorLocatedTypes implements SniffInterface
{

View File

@ -1,54 +0,0 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
trait TraitWithInterfaceImplementation
{
public function getCount($items)
{
return 5;
}
}
interface SomeInterface
{
/**
* @param array $items
* @return int
*/
public function getCount($items);
}
class SomeClassWithInterfaceAndTrait implements SomeInterface
{
use TraitWithInterfaceImplementation;
}
?>
-----
<?php
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Fixture;
trait TraitWithInterfaceImplementation
{
public function getCount(array $items)
{
return 5;
}
}
interface SomeInterface
{
/**
* @return int
*/
public function getCount(array $items);
}
class SomeClassWithInterfaceAndTrait implements SomeInterface
{
use TraitWithInterfaceImplementation;
}
?>

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source;
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\vendor;
abstract class AbstractParentClass
{

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source;
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\ParamTypeDeclarationRector\Source\vendor;
interface SniffInterface
{

View File

@ -239,7 +239,8 @@ final class EregToPcreTransformer
$cls .= $this->_ere2pcre_escape($a) . '\-';
break;
} elseif (ord($a) > ord($b)) {
throw new InvalidEregException(sprintf('an invalid character range %d-%d"', $a, $b));
$errorMessage = sprintf('an invalid character range %d-%d"', (int) $a, (int) $b);
throw new InvalidEregException($errorMessage);
}
$cls .= $this->_ere2pcre_escape($a) . '-' . $this->_ere2pcre_escape($b);
} else {

View File

@ -1,100 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\TypeDeclaration\ChildPopulator;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Type\Type;
use Rector\ChangesReporting\Collector\RectorChangeCollector;
use Rector\NodeCollector\NodeCollector\NodeRepository;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStanStaticTypeMapper\ValueObject\TypeKind;
use Rector\TypeDeclaration\NodeTypeAnalyzer\ChildTypeResolver;
use Rector\TypeDeclaration\ValueObject\NewType;
final class ChildParamPopulator
{
public function __construct(
private NodeNameResolver $nodeNameResolver,
private RectorChangeCollector $rectorChangeCollector,
private NodeRepository $nodeRepository,
private ChildTypeResolver $childTypeResolver
) {
}
/**
* Add typehint to all children
*/
public function populateChildClassMethod(
ClassMethod | Function_ $functionLike,
int $position,
Type $paramType
): void {
if (! $functionLike instanceof ClassMethod) {
return;
}
/** @var string|null $className */
$className = $functionLike->getAttribute(AttributeKey::CLASS_NAME);
// anonymous class
if ($className === null) {
return;
}
$childrenClassLikes = $this->nodeRepository->findClassesAndInterfacesByType($className);
// update their methods as well
foreach ($childrenClassLikes as $childClassLike) {
if ($childClassLike instanceof Class_) {
$usedTraits = $this->nodeRepository->findUsedTraitsInClass($childClassLike);
foreach ($usedTraits as $usedTrait) {
$this->addParamTypeToMethod($usedTrait, $position, $functionLike, $paramType);
}
}
$this->addParamTypeToMethod($childClassLike, $position, $functionLike, $paramType);
}
}
private function addParamTypeToMethod(
ClassLike $classLike,
int $position,
ClassMethod $classMethod,
Type $paramType
): void {
$methodName = $this->nodeNameResolver->getName($classMethod);
$currentClassMethod = $classLike->getMethod($methodName);
if (! $currentClassMethod instanceof ClassMethod) {
return;
}
if (! isset($currentClassMethod->params[$position])) {
return;
}
$paramNode = $currentClassMethod->params[$position];
// already has a type
if ($paramNode->type !== null) {
return;
}
$resolvedChildType = $this->childTypeResolver->resolveChildTypeNode($paramType, TypeKind::PARAM());
if ($resolvedChildType === null) {
return;
}
// let the method know it was changed now
$paramNode->type = $resolvedChildType;
$paramNode->type->setAttribute(NewType::HAS_NEW_INHERITED_TYPE, true);
$this->rectorChangeCollector->notifyNodeFileInfo($paramNode);
}
}

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Rector\TypeDeclaration\Rector\FunctionLike;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
@ -16,7 +15,6 @@ use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DeadCode\PhpDoc\TagRemover\ParamTagRemover;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStanStaticTypeMapper\ValueObject\TypeKind;
use Rector\TypeDeclaration\ChildPopulator\ChildParamPopulator;
use Rector\TypeDeclaration\NodeTypeAnalyzer\TraitTypeAnalyzer;
use Rector\TypeDeclaration\TypeInferer\ParamTypeInferer;
use Rector\TypeDeclaration\ValueObject\NewType;
@ -37,7 +35,7 @@ final class ParamTypeDeclarationRector extends AbstractRector implements MinPhpV
{
public function __construct(
private VendorLockResolver $vendorLockResolver,
private ChildParamPopulator $childParamPopulator,
//private ChildParamPopulator $childParamPopulator,
private ParamTypeInferer $paramTypeInferer,
private TraitTypeAnalyzer $traitTypeAnalyzer,
private ParamTagRemover $paramTagRemover
@ -59,7 +57,7 @@ final class ParamTypeDeclarationRector extends AbstractRector implements MinPhpV
[
new CodeSample(
<<<'CODE_SAMPLE'
class ParentClass
abstract class VendorParentClass
{
/**
* @param int $number
@ -69,7 +67,7 @@ class ParentClass
}
}
final class ChildClass extends ParentClass
final class ChildClass extends VendorParentClass
{
/**
* @param int $number
@ -88,7 +86,7 @@ final class ChildClass extends ParentClass
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class ParentClass
abstract class VendorParentClass
{
/**
* @param int $number
@ -98,7 +96,7 @@ class ParentClass
}
}
final class ChildClass extends ParentClass
final class ChildClass extends VendorParentClass
{
/**
* @param int $number
@ -126,8 +124,10 @@ CODE_SAMPLE
return null;
}
foreach ($node->params as $position => $param) {
$this->refactorParam($param, $node, (int) $position);
// @todo subscribe to Param node directly? narrow scope the better, right?
foreach ($node->params as $param) {
$this->refactorParam($param, $node);
}
return null;
@ -138,7 +138,7 @@ CODE_SAMPLE
return PhpVersionFeature::SCALAR_TYPES;
}
private function refactorParam(Param $param, ClassMethod | Function_ $functionLike, int $position): void
private function refactorParam(Param $param, ClassMethod | Function_ $functionLike): void
{
if ($this->shouldSkipParam($param, $functionLike)) {
return;
@ -154,7 +154,6 @@ CODE_SAMPLE
}
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($inferedType, TypeKind::PARAM());
if (! $paramTypeNode instanceof Node) {
return;
}
@ -168,11 +167,9 @@ CODE_SAMPLE
$functionLikePhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($functionLike);
$this->paramTagRemover->removeParamTagsIfUseless($functionLikePhpDocInfo, $functionLike);
$this->childParamPopulator->populateChildClassMethod($functionLike, $position, $inferedType);
}
private function shouldSkipParam(Param $param, FunctionLike $functionLike): bool
private function shouldSkipParam(Param $param, ClassMethod | Function_ $functionLike): bool
{
if ($param->variadic) {
return true;

View File

@ -8,6 +8,7 @@ use Rector\Core\Configuration\Option;
use Rector\Core\Exception\ShouldNotHappenException;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Throwable;
use Webmozart\Assert\Assert;
final class BootstrapFilesIncluder
{
@ -24,6 +25,9 @@ final class BootstrapFilesIncluder
{
$bootstrapFiles = $this->parameterProvider->provideArrayParameter(Option::BOOTSTRAP_FILES);
Assert::allString($bootstrapFiles);
/** @var string[] $bootstrapFiles */
foreach ($bootstrapFiles as $bootstrapFile) {
if (! is_file($bootstrapFile)) {
throw new ShouldNotHappenException(sprintf('Bootstrap file "%s" does not exist.', $bootstrapFile));

View File

@ -63,7 +63,7 @@ final class ExtensionConfigResolver
$includedFilePath = sprintf(
'%s/%s/%s',
$generatedConfigDirectory,
$extensionConfig['relative_install_path'],
(string) $extensionConfig['relative_install_path'],
$includedFile
);
if (! file_exists($includedFilePath)) {

View File

@ -92,7 +92,7 @@ final class ConsoleApplication extends Application
private function getNewWorkingDir(InputInterface $input): string
{
$workingDir = $input->getParameterOption('--working-dir');
if ($workingDir !== false && ! is_dir($workingDir)) {
if (is_string($workingDir) && ! is_dir($workingDir)) {
$errorMessage = sprintf('Invalid working directory specified, "%s" does not exist.', $workingDir);
throw new InvalidConfigurationException($errorMessage);
}

View File

@ -27,8 +27,8 @@ final class ShouldNotHappenException extends Exception
$debugBacktrace = debug_backtrace();
$class = $debugBacktrace[2]['class'] ?? null;
$function = $debugBacktrace[2]['function'];
$line = $debugBacktrace[1]['line'];
$function = (string) $debugBacktrace[2]['function'];
$line = (int) $debugBacktrace[1]['line'];
$method = $class ? ($class . '::' . $function) : $function;

View File

@ -613,7 +613,7 @@ final class NodeFactory
throw new NotImplementedYetException(sprintf(
'Not implemented yet. Go to "%s()" and add check for "%s" node.',
__METHOD__,
$nodeClass
(string) $nodeClass
));
}