mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 03:40:50 +00:00
[DX] Add RectorConfig upgrade rule to make upgrade easier :) (#2098)
This commit is contained in:
parent
b92465d5c0
commit
d90953d9a9
|
@ -89,6 +89,7 @@ parameters:
|
|||
- rules/Php70/EregToPcreTransformer.php
|
||||
- packages/NodeTypeResolver/NodeTypeResolver.php
|
||||
- rules/Renaming/NodeManipulator/ClassRenamer.php
|
||||
- rules/DogFood/Rector/Closure/UpgradeRectorConfigRector.php
|
||||
|
||||
- "#^Cognitive complexity for \"Rector\\\\Php70\\\\EregToPcreTransformer\\:\\:(.*?)\" is (.*?), keep it under 10$#"
|
||||
- '#Cognitive complexity for "Rector\\Core\\PhpParser\\Node\\Value\\ValueResolver\:\:getValue\(\)" is \d+, keep it under 10#'
|
||||
|
@ -202,6 +203,7 @@ parameters:
|
|||
- '#Access to an undefined property PhpParser\\Node\\FunctionLike\|PhpParser\\Node\\Stmt\\If_\:\:\$stmts#'
|
||||
|
||||
- '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\Isset_\\IssetOnPropertyObjectToPropertyExistsRector\:\:refactor\(\)" is \d+, keep it under 10#'
|
||||
- '#Cognitive complexity for "Rector\\DogFood\\Rector\\Closure\\UpgradeRectorConfigRector\:\:refactor\(\)" is \d+, keep it under 10#'
|
||||
|
||||
- '#(.*?) class\-string, string given#'
|
||||
|
||||
|
@ -249,6 +251,7 @@ parameters:
|
|||
paths:
|
||||
- packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php
|
||||
- rules/Php80/Rector/FunctionLike/UnionTypesRector.php
|
||||
- rules/Php70/Rector/If_/IfToSpaceshipRector.php
|
||||
|
||||
-
|
||||
message: '#Property with protected modifier is not allowed\. Use interface contract method instead#'
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\Fixture;
|
||||
|
||||
use Rector\Renaming\Rector\Name\RenameClassRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(RenameClassRector::class)
|
||||
->configure([
|
||||
'old_class' => 'new_class',
|
||||
]);
|
||||
};
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\Fixture;
|
||||
|
||||
use Rector\Renaming\Rector\Name\RenameClassRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (\Rector\Config\RectorConfig $rectorConfig): void {
|
||||
$rectorConfig
|
||||
->ruleWithConfiguration(RenameClassRector::class, [
|
||||
'old_class' => 'new_class',
|
||||
]);
|
||||
};
|
||||
|
||||
?>
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\Fixture;
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$services = $rectorConfig->services();
|
||||
};
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\Fixture;
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
|
||||
return static function (RectorConfig $rectorConfig) : void {
|
||||
};
|
||||
|
||||
?>
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\Fixture;
|
||||
|
||||
use Rector\Core\Configuration\Option;
|
||||
use Rector\Php74\Rector\Property\TypedPropertyRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$parameters = $containerConfigurator->parameters();
|
||||
$parameters->set(Option::PARALLEL, true);
|
||||
$parameters->set(Option::AUTO_IMPORT_NAMES, true);
|
||||
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(TypedPropertyRector::class);
|
||||
};
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\Fixture;
|
||||
|
||||
use Rector\Core\Configuration\Option;
|
||||
use Rector\Php74\Rector\Property\TypedPropertyRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (\Rector\Config\RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->parallel();
|
||||
$rectorConfig->importNames();
|
||||
$rectorConfig->rule(TypedPropertyRector::class);
|
||||
};
|
||||
|
||||
?>
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class UpgradeRectorConfigRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<SmartFileInfo>
|
||||
*/
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
public function provideConfigFilePath(): string
|
||||
{
|
||||
return __DIR__ . '/config/configured_rule.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\DogFood\Rector\Closure\UpgradeRectorConfigRector;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->rule(UpgradeRectorConfigRector::class);
|
||||
};
|
|
@ -6,6 +6,5 @@ use Rector\Config\RectorConfig;
|
|||
use Rector\Php70\Rector\If_\IfToSpaceshipRector;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$services = $rectorConfig->services();
|
||||
$services->set(IfToSpaceshipRector::class);
|
||||
$rectorConfig->rule(IfToSpaceshipRector::class);
|
||||
};
|
||||
|
|
|
@ -118,20 +118,20 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
// The first value may not be at index 0
|
||||
$tag = reset($tags);
|
||||
$phpDocTagNode = reset($tags);
|
||||
|
||||
if (! in_array($tag->name, $this->annotationsToConsiderForInlining, true)) {
|
||||
if (! in_array($phpDocTagNode->name, $this->annotationsToConsiderForInlining, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_contains((string) $tag, "\n")) {
|
||||
if (str_contains((string) $phpDocTagNode, "\n")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle edge cases where stringified tag is not same as it was originally
|
||||
/** @var Doc $comment */
|
||||
$comment = $comments[0];
|
||||
if (! str_contains($comment->getText(), (string) $tag)) {
|
||||
if (! str_contains($comment->getText(), (string) $phpDocTagNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ CODE_SAMPLE
|
|||
$newPhpDocInfo->makeSingleLined();
|
||||
|
||||
$newPhpDocNode = $newPhpDocInfo->getPhpDocNode();
|
||||
$newPhpDocNode->children = [$tag];
|
||||
$newPhpDocNode->children = [$phpDocTagNode];
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DogFood\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use Rector\Core\Contract\Rector\RectorInterface;
|
||||
use Rector\Core\PhpParser\Node\Value\ValueResolver;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
|
||||
final class ContainerConfiguratorCallAnalyzer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ValueResolver $valueResolver,
|
||||
private readonly NodeNameResolver $nodeNameResolver,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isMethodCallWithServicesSetConfiguredRectorRule(MethodCall $methodCall): bool
|
||||
{
|
||||
return $this->nodeNameResolver->isName($methodCall->name, 'configure');
|
||||
}
|
||||
|
||||
public function isMethodCallWithServicesSetRectorRule(MethodCall $methodCall): bool
|
||||
{
|
||||
if (! $this->isMethodCallNamed($methodCall, 'services', 'set')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstArg = $methodCall->getArgs()[0];
|
||||
$serviceClass = $this->valueResolver->getValue($firstArg->value);
|
||||
|
||||
if (! is_string($serviceClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_a($serviceClass, RectorInterface::class, true);
|
||||
}
|
||||
|
||||
public function isMethodCallNamed(Expr $expr, string $variableName, string $methodName): bool
|
||||
{
|
||||
if (! $expr instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($expr->var, $variableName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->nodeNameResolver->isName($expr->name, $methodName);
|
||||
}
|
||||
}
|
248
rules/DogFood/Rector/Closure/UpgradeRectorConfigRector.php
Normal file
248
rules/DogFood/Rector/Closure/UpgradeRectorConfigRector.php
Normal file
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DogFood\Rector\Closure;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use Rector\Core\Configuration\Option;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\DogFood\NodeAnalyzer\ContainerConfiguratorCallAnalyzer;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @see \Rector\Tests\DogFood\Rector\Closure\UpgradeRectorConfigRector\UpgradeRectorConfigRectorTest
|
||||
*/
|
||||
final class UpgradeRectorConfigRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const PARAMETER_NAME_TO_METHOD_CALL_MAP = [
|
||||
Option::AUTOLOAD_PATHS => 'autoloadPaths',
|
||||
Option::BOOTSTRAP_FILES => 'bootstrapFiles',
|
||||
Option::AUTO_IMPORT_NAMES => 'importNames',
|
||||
Option::PARALLEL => 'parallel',
|
||||
Option::PHPSTAN_FOR_RECTOR_PATH => 'phpstanConfig',
|
||||
Option::PHP_VERSION_FEATURES => 'phpVersion',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const RECTOR_CONFIG_VARIABLE = 'rectorConfig';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const RECTOR_CONFIG_CLASS = 'Rector\Config\RectorConfig';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const PARAMETERS = 'parameters';
|
||||
|
||||
public function __construct(
|
||||
private readonly ContainerConfiguratorCallAnalyzer $containerConfiguratorCallAnalyzer
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('Upgrade rector.php config to use of RectorConfig', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
use Rector\Core\Configuration\Option;
|
||||
use Rector\Php74\Rector\Property\TypedPropertyRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$parameters = $containerConfigurator->parameters();
|
||||
$parameters->set(Option::PARALLEL, true);
|
||||
$parameters->set(Option::AUTO_IMPORT_NAMES, true);
|
||||
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(TypedPropertyRector::class);
|
||||
};
|
||||
CODE_SAMPLE
|
||||
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
use Rector\Php74\Rector\Property\TypedPropertyRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->parallel();
|
||||
$rectorConfig->importNames();
|
||||
|
||||
$rectorConfig->rule(TypedPropertyRector::class);
|
||||
};
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Closure::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$params = $node->getParams();
|
||||
if (count($params) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$onlyParam = $params[0];
|
||||
$paramType = $onlyParam->type;
|
||||
if (! $paramType instanceof Name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isNames($paramType, [
|
||||
'Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator',
|
||||
self::RECTOR_CONFIG_CLASS,
|
||||
])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// update closure params
|
||||
if (! $this->nodeNameResolver->isName($paramType, self::RECTOR_CONFIG_CLASS)) {
|
||||
$onlyParam->type = new FullyQualified(self::RECTOR_CONFIG_CLASS);
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($onlyParam->var, self::RECTOR_CONFIG_VARIABLE)) {
|
||||
$onlyParam->var = new Variable(self::RECTOR_CONFIG_VARIABLE);
|
||||
}
|
||||
|
||||
$this->traverseNodesWithCallable($node->getStmts(), function (Node $node): ?MethodCall {
|
||||
// 1. call on rule
|
||||
if ($node instanceof MethodCall) {
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallWithServicesSetConfiguredRectorRule($node)) {
|
||||
return $this->refactorConfigureRuleMethodCall($node);
|
||||
}
|
||||
|
||||
// look for "$services->set(SomeRector::Class)"
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallWithServicesSetRectorRule($node)) {
|
||||
$node->var = new Variable(self::RECTOR_CONFIG_VARIABLE);
|
||||
$node->name = new Identifier('rule');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallNamed($node, self::PARAMETERS, 'set')) {
|
||||
return $this->refactorParameterName($node);
|
||||
}
|
||||
}
|
||||
|
||||
// look for "$services = $containerConfigurator->services()"
|
||||
$this->removeHelperAssigns($node);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// change the node
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function removeHelperAssigns(Node $node): void
|
||||
{
|
||||
if (! $node instanceof Assign) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallNamed(
|
||||
$node->expr,
|
||||
'containerConfigurator',
|
||||
'services'
|
||||
)) {
|
||||
$this->removeNode($node);
|
||||
}
|
||||
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallNamed(
|
||||
$node->expr,
|
||||
self::RECTOR_CONFIG_VARIABLE,
|
||||
'services'
|
||||
)) {
|
||||
$this->removeNode($node);
|
||||
}
|
||||
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallNamed(
|
||||
$node->expr,
|
||||
'containerConfigurator',
|
||||
self::PARAMETERS
|
||||
)) {
|
||||
$this->removeNode($node);
|
||||
}
|
||||
|
||||
if ($this->containerConfiguratorCallAnalyzer->isMethodCallNamed(
|
||||
$node->expr,
|
||||
self::RECTOR_CONFIG_VARIABLE,
|
||||
self::PARAMETERS
|
||||
)) {
|
||||
$this->removeNode($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Arg[] $args
|
||||
*/
|
||||
private function isOptionWithTrue(array $args, string $optionName): bool
|
||||
{
|
||||
if (! $this->valueResolver->isValue($args[0]->value, $optionName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->valueResolver->isTrue($args[1]->value);
|
||||
}
|
||||
|
||||
private function refactorConfigureRuleMethodCall(MethodCall $methodCall): null|MethodCall
|
||||
{
|
||||
$caller = $methodCall->var;
|
||||
|
||||
if (! $caller instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->containerConfiguratorCallAnalyzer->isMethodCallWithServicesSetRectorRule($caller)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$methodCall->var = new Variable(self::RECTOR_CONFIG_VARIABLE);
|
||||
|
||||
$methodCall->name = new Identifier('ruleWithConfiguration');
|
||||
$methodCall->args = array_merge($caller->getArgs(), $methodCall->getArgs());
|
||||
|
||||
return $methodCall;
|
||||
}
|
||||
|
||||
private function refactorParameterName(MethodCall $methodCall): ?MethodCall
|
||||
{
|
||||
foreach (self::PARAMETER_NAME_TO_METHOD_CALL_MAP as $parameterName => $methodName) {
|
||||
if (! $this->isOptionWithTrue($methodCall->getArgs(), $parameterName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new MethodCall(new Variable(self::RECTOR_CONFIG_VARIABLE), $methodName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -252,19 +252,19 @@ CODE_SAMPLE
|
|||
|
||||
private function processAscendingSort(?Ternary $ternary, Expr $firstValue, Expr $secondValue): Return_
|
||||
{
|
||||
if ($ternary === null || $ternary->cond instanceof Greater) {
|
||||
return $this->processReturnSpaceship($firstValue, $secondValue);
|
||||
if ($ternary instanceof Ternary && ! $ternary->cond instanceof Greater) {
|
||||
return $this->processReturnSpaceship($secondValue, $firstValue);
|
||||
}
|
||||
|
||||
return $this->processReturnSpaceship($secondValue, $firstValue);
|
||||
return $this->processReturnSpaceship($firstValue, $secondValue);
|
||||
}
|
||||
|
||||
private function processDescendingSort(?Ternary $ternary, Expr $firstValue, Expr $secondValue): Return_
|
||||
{
|
||||
if ($ternary === null || $ternary->cond instanceof Smaller) {
|
||||
return $this->processReturnSpaceship($firstValue, $secondValue);
|
||||
if ($ternary instanceof Ternary && ! $ternary->cond instanceof Smaller) {
|
||||
return $this->processReturnSpaceship($secondValue, $firstValue);
|
||||
}
|
||||
|
||||
return $this->processReturnSpaceship($secondValue, $firstValue);
|
||||
return $this->processReturnSpaceship($firstValue, $secondValue);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user