[Naming] Add CLASS_MAP_FILES option + fix missing about-to-be-renamed renamed classes with custom SourceLocator (#303)

This commit is contained in:
Tomas Votruba 2021-06-26 20:19:18 +02:00 committed by GitHub
parent 564cc84d93
commit d808426e71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 185 additions and 98 deletions

View File

@ -137,12 +137,16 @@ return static function (ContainerConfigurator $containerConfigurator): void {
// PHPStan services
$services->set(ReflectionProvider::class)
->factory([service(PHPStanServicesFactory::class), 'createReflectionProvider']);
$services->set(NodeScopeResolver::class)
->factory([service(PHPStanServicesFactory::class), 'createNodeScopeResolver']);
$services->set(ScopeFactory::class)
->factory([service(PHPStanServicesFactory::class), 'createScopeFactory']);
$services->set(TypeNodeResolver::class)
->factory([service(PHPStanServicesFactory::class), 'createTypeNodeResolver']);
$services->set(DynamicSourceLocatorProvider::class)
->factory([service(PHPStanServicesFactory::class), 'createDynamicSourceLocatorProvider']);

View File

@ -19,6 +19,7 @@ use Rector\Core\Reflection\ReflectionResolver;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use ReflectionMethod;
final class MethodParameterTypeResolver
{
@ -105,7 +106,7 @@ final class MethodParameterTypeResolver
$class,
MethodName::CONSTRUCT
);
if ($constructMethodReflection === null) {
if (! $constructMethodReflection instanceof ReflectionMethod) {
continue;
}

View File

@ -14,17 +14,20 @@ use PHPStan\AnalysedCodeException;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
use PHPStan\BetterReflection\Reflector\ClassReflector;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Node\UnreachableStatementNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\Caching\FileSystem\DependencyResolver;
use Rector\Core\Configuration\RenamedClassesDataCollector;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\StaticReflection\SourceLocator\RenamedClassesSourceLocator;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Collector\TraitNodeScopeCollector;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\RemoveDeepChainMethodCallNodeVisitor;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
@ -47,7 +50,8 @@ final class PHPStanNodeScopeResolver
private RemoveDeepChainMethodCallNodeVisitor $removeDeepChainMethodCallNodeVisitor,
private ScopeFactory $scopeFactory,
private TraitNodeScopeCollector $traitNodeScopeCollector,
private RenamedClassesDataCollector $renamedClassesDataCollector
private PrivatesAccessor $privatesAccessor,
private RenamedClassesSourceLocator $renamedClassesSourceLocator,
) {
}
@ -90,12 +94,8 @@ final class PHPStanNodeScopeResolver
}
};
try {
/** @var MutatingScope $scope */
$this->nodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);
} catch (IdentifierNotFound $identifierNotFound) {
$this->reportOrSkip($identifierNotFound);
}
$this->decoratePHPStanNodeScopeResolverWithRenamedClassSourceLocator($this->nodeScopeResolver);
$this->nodeScopeResolver->processNodes($nodes, $scope, $nodeCallback);
$this->resolveAndSaveDependentFiles($nodes, $scope, $smartFileInfo);
@ -170,14 +170,17 @@ final class PHPStanNodeScopeResolver
* That's why we have to skip fatal errors of PHPStan caused by missing class,
* so Rector can fix it first. Then run Rector again to refactor code with new classes.
*/
private function reportOrSkip(IdentifierNotFound $identifierNotFound): void
{
foreach ($this->renamedClassesDataCollector->getOldClasses() as $oldClass) {
if (str_contains($identifierNotFound->getMessage(), $oldClass)) {
return;
}
}
private function decoratePHPStanNodeScopeResolverWithRenamedClassSourceLocator(
NodeScopeResolver $nodeScopeResolver
): void {
// 1. get PHPStan locator
/** @var ClassReflector $classReflector */
$classReflector = $this->privatesAccessor->getPrivateProperty($nodeScopeResolver, 'classReflector');
/** @var SourceLocator $sourceLocator */
$sourceLocator = $this->privatesAccessor->getPrivateProperty($classReflector, 'sourceLocator');
throw $identifierNotFound;
// 2. get Rector locator
$aggregateSourceLocator = new AggregateSourceLocator([$sourceLocator, $this->renamedClassesSourceLocator]);
$this->privatesAccessor->setPrivateProperty($classReflector, 'sourceLocator', $aggregateSourceLocator);
}
}

View File

@ -507,3 +507,9 @@ parameters:
# resolve duplication later
- '#Class with base "StaticTypeMapper" name is already used in "Rector\\StaticTypeMapper\\StaticTypeMapper", "Rector\\PHPStanStaticTypeMapper\\TypeMapper\\StaticTypeMapper"\. Use unique name to make classes easy to recognize#'
- '#Class with base "UnionTypeMapper" name is already used in "Rector\\StaticTypeMapper\\PhpDocParser\\UnionTypeMapper", "Rector\\PHPStanStaticTypeMapper\\TypeMapper\\UnionTypeMapper"\. Use unique name to make classes easy to recognize#'
# allowed for ease api
-
message: '#Cannot return include_once/require_once#'
path: rules/Renaming/Rector/Name/RenameClassRector.php

View File

@ -9,7 +9,7 @@ use Rector\Core\ValueObject\StaticNonPhpFileSuffixes;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class RenameNonPhpTest extends AbstractRectorTestCase
final class NonPhpTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
use Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\NewClass;
use Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\OldClass;
return [
OldClass::class => NewClass::class,
// Laravel
'Session' => 'Illuminate\Support\Facades\Session',
'Form' => 'Collective\Html\FormFacade',
'Html' => 'Collective\Html\HtmlFacade',
];

View File

@ -3,8 +3,6 @@
declare(strict_types=1);
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\NewClass;
use Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\OldClass;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -12,12 +10,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(RenameClassRector::class)
->call('configure', [[
RenameClassRector::OLD_TO_NEW_CLASSES => [
OldClass::class => NewClass::class,
// Laravel
'Session' => 'Illuminate\Support\Facades\Session',
'Form' => 'Collective\Html\FormFacade',
'Html' => 'Collective\Html\HtmlFacade',
],
RenameClassRector::CLASS_MAP_FILES => [__DIR__ . '/files/renamed_classes.php'],
]]);
};

View File

@ -11,10 +11,10 @@ use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\PHPStan\Reflection\CallReflectionResolver;
use Rector\Core\PHPStan\Reflection\ClassMethodReflectionResolver;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\Reflection\ReflectionResolver;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Php80\NodeResolver\ArgumentSorter;
use Rector\Php80\NodeResolver\RequireOptionalParamResolver;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
@ -31,7 +31,6 @@ final class OptionalParametersAfterRequiredRector extends AbstractRector
private RequireOptionalParamResolver $requireOptionalParamResolver,
private ArgumentSorter $argumentSorter,
private CallReflectionResolver $callReflectionResolver,
private ClassMethodReflectionResolver $classMethodReflectionResolver,
private ReflectionResolver $reflectionResolver
) {
}
@ -92,11 +91,25 @@ CODE_SAMPLE
return null;
}
$classMethodReflection = $this->classMethodReflectionResolver->resolve($classMethod);
$classMethod->getAttribute(AttributeKey::CLASS_NAME);
$classMethodReflection = $this->reflectionResolver->resolveMethodReflectionFromClassMethod($classMethod);
if (! $classMethodReflection instanceof MethodReflection) {
return null;
}
$classReflection = $classMethodReflection->getDeclaringClass();
$fileName = $classReflection->getFileName();
// probably internal class
if ($fileName === false) {
return null;
}
if (str_contains($fileName, '/vendor/')) {
return null;
}
$parametersAcceptor = $classMethodReflection->getVariants()[0];
$expectedOrderParameterReflections = $this->requireOptionalParamResolver->resolveFromReflection(
@ -134,6 +147,18 @@ CODE_SAMPLE
return null;
}
$classReflection = $methodReflection->getDeclaringClass();
$fileName = $classReflection->getFileName();
// probably internal class
if ($fileName === false) {
return null;
}
if (str_contains($fileName, '/vendor/')) {
return null;
}
$parametersAcceptor = $methodReflection->getVariants()[0];
$expectedOrderedParameterReflections = $this->requireOptionalParamResolver->resolveFromReflection(

View File

@ -18,6 +18,7 @@ use Rector\Core\Rector\AbstractRector;
use Rector\Renaming\NodeManipulator\ClassRenamer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;
/**
* @see \Rector\Tests\Renaming\Rector\Name\RenameClassRector\RenameClassRectorTest
@ -30,9 +31,10 @@ final class RenameClassRector extends AbstractRector implements ConfigurableRect
public const OLD_TO_NEW_CLASSES = 'old_to_new_classes';
/**
* @var array<string, string>
* @api
* @var string
*/
private array $oldToNewClasses = [];
public const CLASS_MAP_FILES = 'class_map_files';
public function __construct(
private RenamedClassesDataCollector $renamedClassesDataCollector,
@ -100,7 +102,8 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
return $this->classRenamer->renameNode($node, $this->oldToNewClasses);
$oldToNewClasses = $this->renamedClassesDataCollector->getOldToNewClasses();
return $this->classRenamer->renameNode($node, $oldToNewClasses);
}
/**
@ -108,9 +111,27 @@ CODE_SAMPLE
*/
public function configure(array $configuration): void
{
$oldToNewClasses = $configuration[self::OLD_TO_NEW_CLASSES] ?? [];
$this->addOldToNewClasses($configuration[self::OLD_TO_NEW_CLASSES] ?? []);
$classMapFiles = $configuration[self::CLASS_MAP_FILES] ?? [];
Assert::allString($classMapFiles);
foreach ($classMapFiles as $classMapFile) {
Assert::fileExists($classMapFile);
$oldToNewClasses = require_once $classMapFile;
$this->addOldToNewClasses($oldToNewClasses);
}
}
/**
* @param array<string, string> $oldToNewClasses
*/
private function addOldToNewClasses(array $oldToNewClasses): void
{
Assert::allString(array_keys($oldToNewClasses));
Assert::allString($oldToNewClasses);
$this->renamedClassesDataCollector->addOldToNewClasses($oldToNewClasses);
$this->oldToNewClasses = $this->renamedClassesDataCollector->getOldToNewClasses();
}
}

View File

@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\PHPStan\Reflection;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ClassMethodReflectionResolver
{
public function __construct(
private ReflectionProvider $reflectionProvider,
) {
}
public function resolve(ClassMethod $classMethod): ?MethodReflection
{
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return null;
}
if (! $this->reflectionProvider->hasClass($className)) {
return null;
}
$classReflection = $this->reflectionProvider->getClass($className);
$methodName = $classMethod->name->toString();
if (! $classReflection->hasMethod($methodName)) {
return null;
}
return $classReflection->getNativeMethod($methodName);
}
}

View File

@ -39,7 +39,7 @@ final class AstResolver
*
* @var array<class-string, array<string, ClassMethod|null>>
*/
private $classMethodsByClassAndMethod = [];
private array $classMethodsByClassAndMethod = [];
/**
* Parsing files is very heavy performance, so this will help to leverage it
@ -47,7 +47,7 @@ final class AstResolver
*
* @var array<string, Function_|null>>
*/
private $functionsByName = [];
private array $functionsByName = [];
/**
* Parsing files is very heavy performance, so this will help to leverage it
@ -55,7 +55,7 @@ final class AstResolver
*
* @var array<class-string, Class_|null>
*/
private $classesByName = [];
private array $classesByName = [];
public function __construct(
private Parser $parser,
@ -172,7 +172,7 @@ final class AstResolver
public function resolveClassMethod(string $className, string $methodName): ?ClassMethod
{
$methodReflection = $this->reflectionResolver->resolveMethodReflection($className, $methodName);
if ($methodReflection === null) {
if (! $methodReflection instanceof MethodReflection) {
return null;
}

View File

@ -6,12 +6,15 @@ namespace Rector\Core\Reflection;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\TypeWithClassName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
use ReflectionMethod;
final class ReflectionResolver
{
@ -42,7 +45,7 @@ final class ReflectionResolver
/**
* @param class-string $className
*/
public function resolveNativeClassMethodReflection(string $className, string $methodName): ?\ReflectionMethod
public function resolveNativeClassMethodReflection(string $className, string $methodName): ?ReflectionMethod
{
if (! $this->reflectionProvider->hasClass($className)) {
return null;
@ -90,4 +93,15 @@ final class ReflectionResolver
return $this->resolveMethodReflection($callerType->getClassName(), $methodName);
}
public function resolveMethodReflectionFromClassMethod(ClassMethod $classMethod): ?MethodReflection
{
$class = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
if ($class === null) {
return null;
}
$methodName = $this->nodeNameResolver->getName($classMethod);
return $this->resolveMethodReflection($class, $methodName);
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Rector\Core\StaticReflection\SourceLocator;
use PHPStan\BetterReflection\Identifier\Identifier;
use PHPStan\BetterReflection\Identifier\IdentifierType;
use PHPStan\BetterReflection\Reflection\Reflection;
use PHPStan\BetterReflection\Reflection\ReflectionClass;
use PHPStan\BetterReflection\Reflector\ClassReflector;
use PHPStan\BetterReflection\Reflector\Reflector;
use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use Rector\Core\Configuration\RenamedClassesDataCollector;
use Symplify\Astral\ValueObject\NodeBuilder\ClassBuilder;
/**
* Inspired from \PHPStan\BetterReflection\SourceLocator\Type\StringSourceLocator
*/
final class RenamedClassesSourceLocator implements SourceLocator
{
public function __construct(
private RenamedClassesDataCollector $renamedClassesDataCollector,
) {
}
public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
{
foreach ($this->renamedClassesDataCollector->getOldToNewClasses() as $oldClass => $newClass) {
if ($identifier->getName() !== $oldClass) {
continue;
}
// inspired at https://github.com/phpstan/phpstan-src/blob/a9dd9af959fb0c1e0a09d4850f78e05e8dff3d91/src/Reflection/BetterReflection/BetterReflectionProvider.php#L220-L225
return $this->createFakeReflectionClassFromClassName($oldClass);
}
return null;
}
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
{
return [];
}
private function createFakeReflectionClassFromClassName(string $oldClass): ReflectionClass
{
$classBuilder = new ClassBuilder($oldClass);
$class = $classBuilder->getNode();
$fakeLocatedSource = new LocatedSource('virtual', null);
$classReflector = new ClassReflector($this);
return ReflectionClass::createFromNode($classReflector, $class, $fakeLocatedSource);
}
}

View File

@ -5,20 +5,13 @@ declare(strict_types=1);
namespace Rector\Core\Tests\DependencyInjection;
use Iterator;
use Rector\Core\Configuration\RenamedClassesDataCollector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Rector\Testing\PHPUnit\AbstractTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ConfigurableRectorImportConfigCallsMergeTest extends AbstractRectorTestCase
final class ConfigurableRectorImportConfigCallsMergeTest extends AbstractTestCase
{
private PrivatesAccessor $privatesAccessor;
protected function setUp(): void
{
$this->privatesAccessor = new PrivatesAccessor();
}
/**
* @dataProvider provideData()
* @param array<string, string> $expectedConfiguration
@ -28,12 +21,13 @@ final class ConfigurableRectorImportConfigCallsMergeTest extends AbstractRectorT
$configFileInfos = [new SmartFileInfo($config)];
$this->bootFromConfigFileInfos($configFileInfos);
/** @var RenameClassRector $renameClassRector */
$renameClassRector = $this->getService(RenameClassRector::class);
// to invoke configure() method call
$this->getService(RenameClassRector::class);
$oldToNewClasses = $this->privatesAccessor->getPrivateProperty($renameClassRector, 'oldToNewClasses');
/** @var RenamedClassesDataCollector $renamedClassesDataCollector */
$renamedClassesDataCollector = $this->getService(RenamedClassesDataCollector::class);
$this->assertSame($expectedConfiguration, $oldToNewClasses);
$this->assertSame($expectedConfiguration, $renamedClassesDataCollector->getOldToNewClasses());
}
public function provideData(): Iterator
@ -114,9 +108,4 @@ final class ConfigurableRectorImportConfigCallsMergeTest extends AbstractRectorT
],
];
}
public function provideConfigFilePath(): string
{
return '';
}
}