mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-18 05:48:21 +01:00
Change maximal Rector complexity to 35 to avoid hard coupling to Rector classes (#5206)
* complexity * decopule GetSubscriberEventsClassMethodFactory and ListenerServiceDefinitionProvider * decopule UnusedParameterResolver, AssertMethodCallFactory, PropertyFetchWithVariableReplacer * decopule ParamAndArgFromArrayResolver * decopule JsonArrayFactory and JsonEncodeStaticCallFactory * [ci-review] Rector Rectify Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
parent
83d2e1843f
commit
c0105bde25
@ -56,6 +56,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services->alias(SymfonyApplication::class, ConsoleApplication::class);
|
||||
|
||||
$services->set(NoRectorsLoadedReporter::class);
|
||||
$services->set(\Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser::class);
|
||||
|
||||
$services->set(TextDescriptor::class);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Privatization\Rector\Class_\ChangeLocalPropertyToVariableRector;
|
||||
use Rector\Privatization\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector;
|
||||
use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector;
|
||||
use Rector\Privatization\Rector\Class_\MakeUnusedClassesWithChildrenAbstractRector;
|
||||
@ -26,6 +27,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services->set(ChangeReadOnlyVariableWithDefaultValueToConstantRector::class);
|
||||
$services->set(RepeatedLiteralToClassConstantRector::class);
|
||||
|
||||
// $services->set(ChangeLocalPropertyToVariableRector::class);
|
||||
|
||||
$services->set(PrivatizeLocalOnlyMethodRector::class);
|
||||
$services->set(PrivatizeLocalGetterToPropertyRector::class);
|
||||
$services->set(PrivatizeLocalPropertyToPrivatePropertyRector::class);
|
||||
|
@ -408,7 +408,7 @@ parameters:
|
||||
# known values from other methods
|
||||
-
|
||||
message: '#Negated boolean expression is always true#'
|
||||
path: rules/php-spec-to-phpunit/src/Rector/MethodCall/PhpSpecPromisesToPHPUnitAssertRector.php
|
||||
path: rules/php-spec-to-phpunit/src/NodeFactory/AssertMethodCallFactory.php
|
||||
|
||||
-
|
||||
message: '#Call to function in_array\(\) with arguments string, array\(\) and true will always evaluate to false#'
|
||||
@ -521,3 +521,5 @@ parameters:
|
||||
- config/set/*
|
||||
|
||||
- '#Class with base "FileNode" name is already used in "PHPStan\\Node\\FileNode", "Rector\\Core\\PhpParser\\Node\\CustomNode\\FileNode"\. Use unique name to make classes easy to recognize#'
|
||||
|
||||
- '#Content of method "removeFirstArgument\(\)" is duplicated with method "moveArguments\(\)" in "Rector\\PHPUnit\\Rector\\MethodCall\\AssertSameBoolNullToSpecificMethodRector" class\. Use unique content or abstract service instead#'
|
||||
|
48
rules/coding-style/src/NodeAnalyzer/ImplodeAnalyzer.php
Normal file
48
rules/coding-style/src/NodeAnalyzer/ImplodeAnalyzer.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
|
||||
final class ImplodeAnalyzer
|
||||
{
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(NodeNameResolver $nodeNameResolver)
|
||||
{
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches: "implode('","', $items)"
|
||||
*/
|
||||
public function isImplodeToJson(Expr $expr): bool
|
||||
{
|
||||
if (! $expr instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($expr, 'implode')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($expr->args[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstArgumentValue = $expr->args[0]->value;
|
||||
if ($firstArgumentValue instanceof String_ && $firstArgumentValue->value !== '","') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
100
rules/coding-style/src/NodeFactory/JsonArrayFactory.php
Normal file
100
rules/coding-style/src/NodeFactory/JsonArrayFactory.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\NodeFactory;
|
||||
|
||||
use Nette\Utils\Json;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Rector\CodingStyle\NodeAnalyzer\ImplodeAnalyzer;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\Node\NodeFactory;
|
||||
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
|
||||
|
||||
final class JsonArrayFactory
|
||||
{
|
||||
/**
|
||||
* @var NodeFactory
|
||||
*/
|
||||
private $nodeFactory;
|
||||
|
||||
/**
|
||||
* @var ImplodeAnalyzer
|
||||
*/
|
||||
private $implodeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var SimpleCallableNodeTraverser
|
||||
*/
|
||||
private $simpleCallableNodeTraverser;
|
||||
|
||||
public function __construct(
|
||||
NodeFactory $nodeFactory,
|
||||
ImplodeAnalyzer $implodeAnalyzer,
|
||||
SimpleCallableNodeTraverser $simpleCallableNodeTraverser
|
||||
) {
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
$this->implodeAnalyzer = $implodeAnalyzer;
|
||||
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
|
||||
}
|
||||
|
||||
public function createFromJsonString(string $stringValue): Array_
|
||||
{
|
||||
$array = Json::decode($stringValue, Json::FORCE_ARRAY);
|
||||
return $this->nodeFactory->createArray($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expr[] $placeholderNodes
|
||||
*/
|
||||
public function createFromJsonStringAndPlaceholders(string $jsonString, array $placeholderNodes): Array_
|
||||
{
|
||||
$jsonArray = $this->createFromJsonString($jsonString);
|
||||
$this->replaceNodeObjectHashPlaceholdersWithNodes($jsonArray, $placeholderNodes);
|
||||
|
||||
return $jsonArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expr[] $placeholderNodes
|
||||
*/
|
||||
private function replaceNodeObjectHashPlaceholdersWithNodes(Array_ $array, array $placeholderNodes): void
|
||||
{
|
||||
// traverse and replace placeholder by original nodes
|
||||
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($array, function (Node $node) use (
|
||||
$placeholderNodes
|
||||
): ?Expr {
|
||||
if ($node instanceof Array_ && count($node->items) === 1) {
|
||||
$onlyItem = $node->items[0];
|
||||
if ($onlyItem === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$placeholderNode = $this->matchPlaceholderNode($onlyItem->value, $placeholderNodes);
|
||||
|
||||
if ($placeholderNode && $this->implodeAnalyzer->isImplodeToJson($placeholderNode)) {
|
||||
/** @var FuncCall $placeholderNode */
|
||||
return $placeholderNode->args[1]->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->matchPlaceholderNode($node, $placeholderNodes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expr[] $placeholderNodes
|
||||
*/
|
||||
private function matchPlaceholderNode(Node $node, array $placeholderNodes): ?Expr
|
||||
{
|
||||
if (! $node instanceof String_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $placeholderNodes[$node->value] ?? null;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\NodeFactory;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use Rector\Core\PhpParser\Node\NodeFactory;
|
||||
|
||||
/**
|
||||
* Creates + adds
|
||||
*
|
||||
* $jsonData = ['...'];
|
||||
* $json = Nette\Utils\Json::encode($jsonData);
|
||||
*/
|
||||
final class JsonEncodeStaticCallFactory
|
||||
{
|
||||
/**
|
||||
* @var NodeFactory
|
||||
*/
|
||||
private $nodeFactory;
|
||||
|
||||
public function __construct(NodeFactory $nodeFactory)
|
||||
{
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates + adds
|
||||
*
|
||||
* $jsonData = ['...'];
|
||||
* $json = Nette\Utils\Json::encode($jsonData);
|
||||
*/
|
||||
public function createFromArray(Expr $assignExpr, Array_ $jsonArray): Assign
|
||||
{
|
||||
$jsonDataAssign = new Assign($assignExpr, $jsonArray);
|
||||
|
||||
$jsonDataVariable = new Variable('jsonData');
|
||||
$jsonDataAssign->expr = $this->nodeFactory->createStaticCall('Nette\Utils\Json', 'encode', [$jsonDataVariable]);
|
||||
|
||||
return $jsonDataAssign;
|
||||
}
|
||||
}
|
@ -9,19 +9,18 @@ use Nette\Utils\JsonException;
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\AssignOp\Concat as ConcatAssign;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use Rector\CodingStyle\Node\ConcatJoiner;
|
||||
use Rector\CodingStyle\Node\ConcatManipulator;
|
||||
use Rector\CodingStyle\NodeFactory\JsonArrayFactory;
|
||||
use Rector\CodingStyle\NodeFactory\JsonEncodeStaticCallFactory;
|
||||
use Rector\CodingStyle\ValueObject\ConcatExpressionJoinData;
|
||||
use Rector\CodingStyle\ValueObject\NodeToRemoveAndConcatItem;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
@ -56,10 +55,26 @@ final class ManualJsonStringToJsonEncodeArrayRector extends AbstractRector
|
||||
*/
|
||||
private $concatManipulator;
|
||||
|
||||
public function __construct(ConcatJoiner $concatJoiner, ConcatManipulator $concatManipulator)
|
||||
{
|
||||
/**
|
||||
* @var JsonEncodeStaticCallFactory
|
||||
*/
|
||||
private $jsonEncodeStaticCallFactory;
|
||||
|
||||
/**
|
||||
* @var JsonArrayFactory
|
||||
*/
|
||||
private $jsonArrayFactory;
|
||||
|
||||
public function __construct(
|
||||
ConcatJoiner $concatJoiner,
|
||||
ConcatManipulator $concatManipulator,
|
||||
JsonEncodeStaticCallFactory $jsonEncodeStaticCallFactory,
|
||||
JsonArrayFactory $jsonArrayFactory
|
||||
) {
|
||||
$this->concatJoiner = $concatJoiner;
|
||||
$this->concatManipulator = $concatManipulator;
|
||||
$this->jsonEncodeStaticCallFactory = $jsonEncodeStaticCallFactory;
|
||||
$this->jsonArrayFactory = $jsonArrayFactory;
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
@ -77,6 +92,8 @@ final class SomeClass
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
use Nette\Utils\Json;
|
||||
|
||||
final class SomeClass
|
||||
{
|
||||
public function run()
|
||||
@ -85,8 +102,7 @@ final class SomeClass
|
||||
'role_name' => 'admin',
|
||||
'numberz' => ['id' => 10]
|
||||
];
|
||||
|
||||
$someJsonAsString = Nette\Utils\Json::encode($data);
|
||||
$someJsonAsString = Json::encode($data);
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
@ -113,7 +129,15 @@ CODE_SAMPLE
|
||||
// A. full json string
|
||||
$isJsonString = $this->isJsonString($stringValue);
|
||||
if ($isJsonString) {
|
||||
return $this->processJsonString($node, $stringValue);
|
||||
$jsonArray = $this->jsonArrayFactory->createFromJsonString($stringValue);
|
||||
$jsonEncodeAssign = $this->jsonEncodeStaticCallFactory->createFromArray($node->var, $jsonArray);
|
||||
|
||||
$jsonDataVariable = new Variable('jsonData');
|
||||
$jsonDataAssign = new Assign($jsonDataVariable, $jsonArray);
|
||||
|
||||
$this->addNodeBeforeNode($jsonDataAssign, $node);
|
||||
|
||||
return $jsonEncodeAssign;
|
||||
}
|
||||
|
||||
// B. just start of a json? join with all the strings that concat so same variable
|
||||
@ -173,13 +197,6 @@ CODE_SAMPLE
|
||||
}
|
||||
}
|
||||
|
||||
private function processJsonString(Assign $assign, string $stringValue): Node
|
||||
{
|
||||
$arrayNode = $this->createArrayNodeFromJsonString($stringValue);
|
||||
|
||||
return $this->createAndReturnJsonEncodeFromArray($assign, $arrayNode);
|
||||
}
|
||||
|
||||
private function collectContentAndPlaceholderNodesFromNextExpressions(Assign $assign): ConcatExpressionJoinData
|
||||
{
|
||||
$concatExpressionJoinData = new ConcatExpressionJoinData();
|
||||
@ -239,35 +256,13 @@ CODE_SAMPLE
|
||||
|
||||
$this->removeNodes($nodesToRemove);
|
||||
|
||||
$jsonArray = $this->createArrayNodeFromJsonString($stringValue);
|
||||
$this->replaceNodeObjectHashPlaceholdersWithNodes($jsonArray, $placeholderNodes);
|
||||
|
||||
return $this->createAndReturnJsonEncodeFromArray($assign, $jsonArray);
|
||||
}
|
||||
|
||||
private function createArrayNodeFromJsonString(string $stringValue): Array_
|
||||
{
|
||||
$array = Json::decode($stringValue, Json::FORCE_ARRAY);
|
||||
|
||||
return $this->createArray($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates + adds
|
||||
*
|
||||
* $jsonData = ['...'];
|
||||
* $json = Nette\Utils\Json::encode($jsonData);
|
||||
*/
|
||||
private function createAndReturnJsonEncodeFromArray(Assign $assign, Array_ $jsonArray): Assign
|
||||
{
|
||||
$jsonArray = $this->jsonArrayFactory->createFromJsonStringAndPlaceholders($stringValue, $placeholderNodes);
|
||||
$jsonDataVariable = new Variable('jsonData');
|
||||
|
||||
$jsonDataAssign = new Assign($jsonDataVariable, $jsonArray);
|
||||
|
||||
$this->addNodeBeforeNode($jsonDataAssign, $assign);
|
||||
|
||||
$assign->expr = $this->createStaticCall('Nette\Utils\Json', 'encode', [$jsonDataVariable]);
|
||||
|
||||
return $assign;
|
||||
return $this->jsonEncodeStaticCallFactory->createFromArray($assign->var, $jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -316,66 +311,4 @@ CODE_SAMPLE
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expr[] $placeholderNodes
|
||||
*/
|
||||
private function replaceNodeObjectHashPlaceholdersWithNodes(Array_ $array, array $placeholderNodes): void
|
||||
{
|
||||
// traverse and replace placeholder by original nodes
|
||||
$this->traverseNodesWithCallable($array, function (Node $node) use ($placeholderNodes): ?Expr {
|
||||
if ($node instanceof Array_ && count($node->items) === 1) {
|
||||
$onlyItem = $node->items[0];
|
||||
if ($onlyItem === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$placeholderNode = $this->matchPlaceholderNode($onlyItem->value, $placeholderNodes);
|
||||
|
||||
if ($placeholderNode && $this->isImplodeToJson($placeholderNode)) {
|
||||
/** @var FuncCall $placeholderNode */
|
||||
return $placeholderNode->args[1]->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->matchPlaceholderNode($node, $placeholderNodes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expr[] $placeholderNodes
|
||||
*/
|
||||
private function matchPlaceholderNode(Node $node, array $placeholderNodes): ?Expr
|
||||
{
|
||||
if (! $node instanceof String_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $placeholderNodes[$node->value] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches: "implode('","', $items)"
|
||||
*/
|
||||
private function isImplodeToJson(Expr $expr): bool
|
||||
{
|
||||
if (! $expr instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->isName($expr, 'implode')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($expr->args[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstArgumentValue = $expr->args[0]->value;
|
||||
if ($firstArgumentValue instanceof String_ && $firstArgumentValue->value !== '","') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
112
rules/dead-code/src/NodeCollector/UnusedParameterResolver.php
Normal file
112
rules/dead-code/src/NodeCollector/UnusedParameterResolver.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\NodeCollector;
|
||||
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassMethodManipulator;
|
||||
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
|
||||
use Rector\Core\ValueObject\MethodName;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
|
||||
final class UnusedParameterResolver
|
||||
{
|
||||
/**
|
||||
* @var ClassMethodManipulator
|
||||
*/
|
||||
private $classMethodManipulator;
|
||||
|
||||
/**
|
||||
* @var BetterStandardPrinter
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(
|
||||
ClassMethodManipulator $classMethodManipulator,
|
||||
NodeNameResolver $nodeNameResolver,
|
||||
BetterStandardPrinter $betterStandardPrinter
|
||||
) {
|
||||
$this->classMethodManipulator = $classMethodManipulator;
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_[] $childrenOfClass
|
||||
* @return Param[]
|
||||
*/
|
||||
public function resolve(ClassMethod $classMethod, string $methodName, array $childrenOfClass): array
|
||||
{
|
||||
$unusedParameters = $this->resolveUnusedParameters($classMethod);
|
||||
if ($unusedParameters === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($childrenOfClass as $childClassNode) {
|
||||
$methodOfChild = $childClassNode->getMethod($methodName);
|
||||
if ($methodOfChild === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unusedParameters = $this->getParameterOverlap(
|
||||
$unusedParameters,
|
||||
$this->resolveUnusedParameters($methodOfChild)
|
||||
);
|
||||
}
|
||||
|
||||
return $unusedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Param[]
|
||||
*/
|
||||
private function resolveUnusedParameters(ClassMethod $classMethod): array
|
||||
{
|
||||
$unusedParameters = [];
|
||||
|
||||
foreach ($classMethod->params as $i => $param) {
|
||||
// skip property promotion
|
||||
/** @var Param $param */
|
||||
if ($param->flags !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->classMethodManipulator->isParameterUsedInClassMethod($param, $classMethod)) {
|
||||
// reset to keep order of removed arguments, if not construtctor - probably autowired
|
||||
if (! $this->nodeNameResolver->isName($classMethod, MethodName::CONSTRUCT)) {
|
||||
$unusedParameters = [];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$unusedParameters[$i] = $param;
|
||||
}
|
||||
|
||||
return $unusedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Param[] $parameters1
|
||||
* @param Param[] $parameters2
|
||||
* @return Param[]
|
||||
*/
|
||||
private function getParameterOverlap(array $parameters1, array $parameters2): array
|
||||
{
|
||||
return array_uintersect(
|
||||
$parameters1,
|
||||
$parameters2,
|
||||
function (Param $firstParam, Param $secondParam): int {
|
||||
return $this->betterStandardPrinter->areNodesEqual($firstParam, $secondParam) ? 0 : 1;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -11,9 +11,8 @@ use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassMethodManipulator;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\ValueObject\MethodName;
|
||||
use Rector\DeadCode\NodeCollector\UnusedParameterResolver;
|
||||
use Rector\DeadCode\NodeManipulator\MagicMethodDetector;
|
||||
use Rector\DeadCode\NodeManipulator\VariadicFunctionLikeDetector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
@ -33,11 +32,6 @@ final class RemoveUnusedParameterRector extends AbstractRector implements ZeroCa
|
||||
*/
|
||||
private $classManipulator;
|
||||
|
||||
/**
|
||||
* @var ClassMethodManipulator
|
||||
*/
|
||||
private $classMethodManipulator;
|
||||
|
||||
/**
|
||||
* @var MagicMethodDetector
|
||||
*/
|
||||
@ -48,16 +42,21 @@ final class RemoveUnusedParameterRector extends AbstractRector implements ZeroCa
|
||||
*/
|
||||
private $variadicFunctionLikeDetector;
|
||||
|
||||
/**
|
||||
* @var UnusedParameterResolver
|
||||
*/
|
||||
private $unusedParameterResolver;
|
||||
|
||||
public function __construct(
|
||||
ClassManipulator $classManipulator,
|
||||
ClassMethodManipulator $classMethodManipulator,
|
||||
MagicMethodDetector $magicMethodDetector,
|
||||
VariadicFunctionLikeDetector $variadicFunctionLikeDetector
|
||||
VariadicFunctionLikeDetector $variadicFunctionLikeDetector,
|
||||
UnusedParameterResolver $unusedParameterResolver
|
||||
) {
|
||||
$this->classManipulator = $classManipulator;
|
||||
$this->classMethodManipulator = $classMethodManipulator;
|
||||
$this->magicMethodDetector = $magicMethodDetector;
|
||||
$this->variadicFunctionLikeDetector = $variadicFunctionLikeDetector;
|
||||
$this->unusedParameterResolver = $unusedParameterResolver;
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
@ -119,7 +118,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
$childrenOfClass = $this->nodeRepository->findChildrenOfClass($className);
|
||||
$unusedParameters = $this->getUnusedParameters($node, $methodName, $childrenOfClass);
|
||||
$unusedParameters = $this->unusedParameterResolver->resolve($node, $methodName, $childrenOfClass);
|
||||
if ($unusedParameters === []) {
|
||||
return null;
|
||||
}
|
||||
@ -176,48 +175,6 @@ CODE_SAMPLE
|
||||
return $this->isAnonymousClass($classLike);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_[] $childrenOfClass
|
||||
* @return Param[]
|
||||
*/
|
||||
private function getUnusedParameters(ClassMethod $classMethod, string $methodName, array $childrenOfClass): array
|
||||
{
|
||||
$unusedParameters = $this->resolveUnusedParameters($classMethod);
|
||||
if ($unusedParameters === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($childrenOfClass as $childClassNode) {
|
||||
$methodOfChild = $childClassNode->getMethod($methodName);
|
||||
if ($methodOfChild === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unusedParameters = $this->getParameterOverlap(
|
||||
$unusedParameters,
|
||||
$this->resolveUnusedParameters($methodOfChild)
|
||||
);
|
||||
}
|
||||
|
||||
return $unusedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Param[] $parameters1
|
||||
* @param Param[] $parameters2
|
||||
* @return Param[]
|
||||
*/
|
||||
private function getParameterOverlap(array $parameters1, array $parameters2): array
|
||||
{
|
||||
return array_uintersect(
|
||||
$parameters1,
|
||||
$parameters2,
|
||||
function (Param $firstParam, Param $secondParam): int {
|
||||
return $this->areNodesEqual($firstParam, $secondParam) ? 0 : 1;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Param[] $unusedParameters
|
||||
*/
|
||||
@ -301,31 +258,18 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Param[] $parameters1
|
||||
* @param Param[] $parameters2
|
||||
* @return Param[]
|
||||
*/
|
||||
private function resolveUnusedParameters(ClassMethod $classMethod): array
|
||||
private function getParameterOverlap(array $parameters1, array $parameters2): array
|
||||
{
|
||||
$unusedParameters = [];
|
||||
|
||||
foreach ($classMethod->params as $i => $param) {
|
||||
// skip property promotion
|
||||
/** @var Param $param */
|
||||
if ($param->flags !== 0) {
|
||||
continue;
|
||||
return array_uintersect(
|
||||
$parameters1,
|
||||
$parameters2,
|
||||
function (Param $firstParam, Param $secondParam): int {
|
||||
return $this->betterStandardPrinter->areNodesEqual($firstParam, $secondParam) ? 0 : 1;
|
||||
}
|
||||
|
||||
if ($this->classMethodManipulator->isParameterUsedInClassMethod($param, $classMethod)) {
|
||||
// reset to keep order of removed arguments, if not construtctor - probably autowired
|
||||
if (! $this->isName($classMethod, MethodName::CONSTRUCT)) {
|
||||
$unusedParameters = [];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$unusedParameters[$i] = $param;
|
||||
}
|
||||
|
||||
return $unusedParameters;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDeadRecursiveClassMethodRector\Fixture;
|
||||
|
||||
class StaticCall
|
||||
class SomeStaticCall
|
||||
{
|
||||
public static function run()
|
||||
{
|
||||
@ -16,7 +16,7 @@ class StaticCall
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\ClassMethod\RemoveDeadRecursiveClassMethodRector\Fixture;
|
||||
|
||||
class StaticCall
|
||||
class SomeStaticCall
|
||||
{
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\PhpSpecToPHPUnit\NodeFactory;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ConstFetchManipulator;
|
||||
use Rector\Core\PhpParser\Node\NodeFactory;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
|
||||
final class AssertMethodCallFactory
|
||||
{
|
||||
/**
|
||||
* @var NodeFactory
|
||||
*/
|
||||
private $nodeFactory;
|
||||
|
||||
/**
|
||||
* @var ConstFetchManipulator
|
||||
*/
|
||||
private $constFetchManipulator;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isBoolAssert = false;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(
|
||||
NodeFactory $nodeFactory,
|
||||
ConstFetchManipulator $constFetchManipulator,
|
||||
NodeNameResolver $nodeNameResolver
|
||||
) {
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
$this->constFetchManipulator = $constFetchManipulator;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
public function createAssertMethod(
|
||||
string $name,
|
||||
Expr $value,
|
||||
?Expr $expected,
|
||||
PropertyFetch $testedObjectPropertyFetch
|
||||
): MethodCall {
|
||||
$this->isBoolAssert = false;
|
||||
|
||||
// special case with bool!
|
||||
if ($expected !== null) {
|
||||
$name = $this->resolveBoolMethodName($name, $expected);
|
||||
}
|
||||
|
||||
$assetMethodCall = $this->nodeFactory->createMethodCall('this', $name);
|
||||
|
||||
if (! $this->isBoolAssert && $expected) {
|
||||
$assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch(
|
||||
$expected,
|
||||
$testedObjectPropertyFetch
|
||||
));
|
||||
}
|
||||
|
||||
$assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($value, $testedObjectPropertyFetch));
|
||||
|
||||
return $assetMethodCall;
|
||||
}
|
||||
|
||||
private function resolveBoolMethodName(string $name, Expr $expr): string
|
||||
{
|
||||
if (! $this->constFetchManipulator->isBool($expr)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($name === 'assertSame') {
|
||||
$this->isBoolAssert = true;
|
||||
return $this->constFetchManipulator->isFalse($expr) ? 'assertFalse' : 'assertTrue';
|
||||
}
|
||||
|
||||
if ($name === 'assertNotSame') {
|
||||
$this->isBoolAssert = true;
|
||||
return $this->constFetchManipulator->isFalse($expr) ? 'assertNotFalse' : 'assertNotTrue';
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function thisToTestedObjectPropertyFetch(Expr $expr, PropertyFetch $propertyFetch): Expr
|
||||
{
|
||||
if (! $expr instanceof Variable) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($expr, 'this')) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
return $propertyFetch;
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ namespace Rector\PhpSpecToPHPUnit\Rector\MethodCall;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
@ -26,6 +25,7 @@ use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpSpecToPHPUnit\MatchersManipulator;
|
||||
use Rector\PhpSpecToPHPUnit\Naming\PhpSpecRenaming;
|
||||
use Rector\PhpSpecToPHPUnit\NodeFactory\AssertMethodCallFactory;
|
||||
use Rector\PhpSpecToPHPUnit\Rector\AbstractPhpSpecToPHPUnitRector;
|
||||
|
||||
/**
|
||||
@ -37,7 +37,7 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
* @see https://github.com/phpspec/phpspec/blob/master/src/PhpSpec/Wrapper/Subject.php
|
||||
* ↓
|
||||
* @see https://phpunit.readthedocs.io/en/8.0/assertions.html
|
||||
* @var string[][]
|
||||
* @var array<string, string[]>
|
||||
*/
|
||||
private const NEW_METHOD_TO_OLD_METHODS = [
|
||||
'assertInstanceOf' => ['shouldBeAnInstanceOf', 'shouldHaveType', 'shouldReturnAnInstanceOf'],
|
||||
@ -86,11 +86,6 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
*/
|
||||
private $testedClass;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isBoolAssert = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
@ -116,10 +111,19 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
*/
|
||||
private $matchersManipulator;
|
||||
|
||||
public function __construct(MatchersManipulator $matchersManipulator, PhpSpecRenaming $phpSpecRenaming)
|
||||
{
|
||||
/**
|
||||
* @var AssertMethodCallFactory
|
||||
*/
|
||||
private $assertMethodCallFactory;
|
||||
|
||||
public function __construct(
|
||||
MatchersManipulator $matchersManipulator,
|
||||
PhpSpecRenaming $phpSpecRenaming,
|
||||
AssertMethodCallFactory $assertMethodCallFactory
|
||||
) {
|
||||
$this->phpSpecRenaming = $phpSpecRenaming;
|
||||
$this->matchersManipulator = $matchersManipulator;
|
||||
$this->assertMethodCallFactory = $assertMethodCallFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,7 +151,7 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
}
|
||||
|
||||
if ($this->isName($node->name, 'during')) {
|
||||
return $this->processDuring($node);
|
||||
return $this->processDuringMethodCall($node);
|
||||
}
|
||||
|
||||
if ($this->isName($node->name, 'duringInstantiation')) {
|
||||
@ -168,7 +172,12 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
|
||||
foreach (self::NEW_METHOD_TO_OLD_METHODS as $newMethod => $oldMethods) {
|
||||
if ($this->isNames($node->name, $oldMethods)) {
|
||||
return $this->createAssertMethod($newMethod, $node->var, $node->args[0]->value ?? null);
|
||||
return $this->assertMethodCallFactory->createAssertMethod(
|
||||
$newMethod,
|
||||
$node->var,
|
||||
$node->args[0]->value ?? null,
|
||||
$this->testedObjectPropertyFetch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +206,7 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function processDuring(MethodCall $methodCall): MethodCall
|
||||
private function processDuringMethodCall(MethodCall $methodCall): MethodCall
|
||||
{
|
||||
if (! isset($methodCall->args[0])) {
|
||||
throw new ShouldNotHappenException();
|
||||
@ -308,26 +317,6 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
}
|
||||
}
|
||||
|
||||
private function createAssertMethod(string $name, Expr $value, ?Expr $expected): MethodCall
|
||||
{
|
||||
$this->isBoolAssert = false;
|
||||
|
||||
// special case with bool!
|
||||
if ($expected !== null) {
|
||||
$name = $this->resolveBoolMethodName($name, $expected);
|
||||
}
|
||||
|
||||
$assetMethodCall = $this->createMethodCall(self::THIS, $name);
|
||||
|
||||
if (! $this->isBoolAssert && $expected) {
|
||||
$assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($expected));
|
||||
}
|
||||
|
||||
$assetMethodCall->args[] = new Arg($this->thisToTestedObjectPropertyFetch($value));
|
||||
|
||||
return $assetMethodCall;
|
||||
}
|
||||
|
||||
private function shouldSkip(MethodCall $methodCall): bool
|
||||
{
|
||||
if (! $this->isVariableName($methodCall->var, self::THIS)) {
|
||||
@ -365,32 +354,4 @@ final class PhpSpecPromisesToPHPUnitAssertRector extends AbstractPhpSpecToPHPUni
|
||||
$staticCall->args[] = new Arg($arrayItem->value);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveBoolMethodName(string $name, Expr $expr): string
|
||||
{
|
||||
if (! $this->isBool($expr)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($name === 'assertSame') {
|
||||
$this->isBoolAssert = true;
|
||||
return $this->isFalse($expr) ? 'assertFalse' : 'assertTrue';
|
||||
}
|
||||
|
||||
if ($name === 'assertNotSame') {
|
||||
$this->isBoolAssert = true;
|
||||
return $this->isFalse($expr) ? 'assertNotFalse' : 'assertNotTrue';
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function thisToTestedObjectPropertyFetch(Expr $expr): Expr
|
||||
{
|
||||
if (! $this->isVariableName($expr, self::THIS)) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
return $this->testedObjectPropertyFetch;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPUnit\NodeManipulator;
|
||||
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||
use Rector\PHPUnit\ValueObject\ParamAndArg;
|
||||
|
||||
final class ParamAndArgFromArrayResolver
|
||||
{
|
||||
/**
|
||||
* @var NodeTypeResolver
|
||||
*/
|
||||
private $nodeTypeResolver;
|
||||
|
||||
/**
|
||||
* @var TypeFactory
|
||||
*/
|
||||
private $typeFactory;
|
||||
|
||||
public function __construct(NodeTypeResolver $nodeTypeResolver, TypeFactory $typeFactory)
|
||||
{
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
$this->typeFactory = $typeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArg[]
|
||||
*/
|
||||
public function resolve(Array_ $array, string $variableName): array
|
||||
{
|
||||
$isNestedArray = $this->isNestedArray($array);
|
||||
if ($isNestedArray) {
|
||||
return $this->collectParamAndArgsFromNestedArray($array, $variableName);
|
||||
}
|
||||
|
||||
$itemsStaticType = $this->resolveItemStaticType($array, $isNestedArray);
|
||||
return $this->collectParamAndArgsFromNonNestedArray($array, $variableName, $itemsStaticType);
|
||||
}
|
||||
|
||||
private function isNestedArray(Array_ $array): bool
|
||||
{
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($arrayItem->value instanceof Array_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArg[]
|
||||
*/
|
||||
private function collectParamAndArgsFromNestedArray(Array_ $array, string $variableName): array
|
||||
{
|
||||
$paramAndArgs = [];
|
||||
$i = 1;
|
||||
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nestedArray = $arrayItem->value;
|
||||
if (! $nestedArray instanceof Array_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($nestedArray->items as $nestedArrayItem) {
|
||||
if (! $nestedArrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variable = new Variable($variableName . ($i === 1 ? '' : $i));
|
||||
|
||||
$itemsStaticType = $this->nodeTypeResolver->getStaticType($nestedArrayItem->value);
|
||||
$paramAndArgs[] = new ParamAndArg($variable, $itemsStaticType);
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
return $paramAndArgs;
|
||||
}
|
||||
|
||||
private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type
|
||||
{
|
||||
$staticTypes = [];
|
||||
if (! $isNestedArray) {
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$staticTypes[] = $this->nodeTypeResolver->getStaticType($arrayItem->value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($staticTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArg[]
|
||||
*/
|
||||
private function collectParamAndArgsFromNonNestedArray(
|
||||
Array_ $array,
|
||||
string $variableName,
|
||||
Type $itemsStaticType
|
||||
): array {
|
||||
$i = 1;
|
||||
$paramAndArgs = [];
|
||||
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variable = new Variable($variableName . ($i === 1 ? '' : $i));
|
||||
|
||||
$paramAndArgs[] = new ParamAndArg($variable, $itemsStaticType);
|
||||
++$i;
|
||||
|
||||
if (! $arrayItem->value instanceof Array_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $paramAndArgs;
|
||||
}
|
||||
}
|
@ -7,9 +7,7 @@ namespace Rector\PHPUnit\Rector\Class_;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
@ -17,7 +15,6 @@ use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareParamTagValueNode;
|
||||
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
|
||||
@ -26,8 +23,8 @@ use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\Rector\AbstractPHPUnitRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||
use Rector\PHPUnit\NodeFactory\DataProviderClassMethodFactory;
|
||||
use Rector\PHPUnit\NodeManipulator\ParamAndArgFromArrayResolver;
|
||||
use Rector\PHPUnit\ValueObject\ArrayArgumentToDataProvider;
|
||||
use Rector\PHPUnit\ValueObject\DataProviderClassMethodRecipe;
|
||||
use Rector\PHPUnit\ValueObject\ParamAndArg;
|
||||
@ -64,16 +61,16 @@ final class ArrayArgumentInTestToDataProviderRector extends AbstractPHPUnitRecto
|
||||
private $dataProviderClassMethodFactory;
|
||||
|
||||
/**
|
||||
* @var TypeFactory
|
||||
* @var ParamAndArgFromArrayResolver
|
||||
*/
|
||||
private $typeFactory;
|
||||
private $paramAndArgFromArrayResolver;
|
||||
|
||||
public function __construct(
|
||||
DataProviderClassMethodFactory $dataProviderClassMethodFactory,
|
||||
TypeFactory $typeFactory
|
||||
ParamAndArgFromArrayResolver $paramAndArgFromArrayResolver
|
||||
) {
|
||||
$this->dataProviderClassMethodFactory = $dataProviderClassMethodFactory;
|
||||
$this->typeFactory = $typeFactory;
|
||||
$this->paramAndArgFromArrayResolver = $paramAndArgFromArrayResolver;
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
@ -209,7 +206,7 @@ CODE_SAMPLE
|
||||
|
||||
$methodCall->args = [];
|
||||
|
||||
$paramAndArgs = $this->collectParamAndArgsFromArray(
|
||||
$paramAndArgs = $this->paramAndArgFromArrayResolver->resolve(
|
||||
$firstArgumentValue,
|
||||
$arrayArgumentToDataProvider->getVariableName()
|
||||
);
|
||||
@ -266,20 +263,6 @@ CODE_SAMPLE
|
||||
return 'provideDataFor' . ucfirst($methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArg[]
|
||||
*/
|
||||
private function collectParamAndArgsFromArray(Array_ $array, string $variableName): array
|
||||
{
|
||||
$isNestedArray = $this->isNestedArray($array);
|
||||
if ($isNestedArray) {
|
||||
return $this->collectParamAndArgsFromNestedArray($array, $variableName);
|
||||
}
|
||||
|
||||
$itemsStaticType = $this->resolveItemStaticType($array, $isNestedArray);
|
||||
return $this->collectParamAndArgsFromNonNestedArray($array, $variableName, $itemsStaticType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ParamAndArg[] $paramAndArgs
|
||||
*/
|
||||
@ -315,99 +298,6 @@ CODE_SAMPLE
|
||||
));
|
||||
}
|
||||
|
||||
private function isNestedArray(Array_ $array): bool
|
||||
{
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($arrayItem->value instanceof Array_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArg[]
|
||||
*/
|
||||
private function collectParamAndArgsFromNestedArray(Array_ $array, string $variableName): array
|
||||
{
|
||||
$paramAndArgs = [];
|
||||
$i = 1;
|
||||
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nestedArray = $arrayItem->value;
|
||||
if (! $nestedArray instanceof Array_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($nestedArray->items as $nestedArrayItem) {
|
||||
if (! $nestedArrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variable = new Variable($variableName . ($i === 1 ? '' : $i));
|
||||
|
||||
$itemsStaticType = $this->getStaticType($nestedArrayItem->value);
|
||||
$paramAndArgs[] = new ParamAndArg($variable, $itemsStaticType);
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
return $paramAndArgs;
|
||||
}
|
||||
|
||||
private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type
|
||||
{
|
||||
$staticTypes = [];
|
||||
if (! $isNestedArray) {
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$staticTypes[] = $this->getStaticType($arrayItem->value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($staticTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArg[]
|
||||
*/
|
||||
private function collectParamAndArgsFromNonNestedArray(
|
||||
Array_ $array,
|
||||
string $variableName,
|
||||
Type $itemsStaticType
|
||||
): array {
|
||||
$i = 1;
|
||||
$paramAndArgs = [];
|
||||
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variable = new Variable($variableName . ($i === 1 ? '' : $i));
|
||||
|
||||
$paramAndArgs[] = new ParamAndArg($variable, $itemsStaticType);
|
||||
++$i;
|
||||
|
||||
if (! $arrayItem->value instanceof Array_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $paramAndArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ParamAndArg[] $paramAndArgs
|
||||
* @return Param[]
|
||||
@ -417,7 +307,6 @@ CODE_SAMPLE
|
||||
$params = [];
|
||||
foreach ($paramAndArgs as $paramAndArg) {
|
||||
$param = new Param($paramAndArg->getVariable());
|
||||
|
||||
$this->setTypeIfNotNull($paramAndArg, $param);
|
||||
|
||||
$params[] = $param;
|
||||
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Privatization\NodeReplacer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
|
||||
|
||||
final class PropertyFetchWithVariableReplacer
|
||||
{
|
||||
/**
|
||||
* @var SimpleCallableNodeTraverser
|
||||
*/
|
||||
private $simpleCallableNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var NodeNameResolver
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(
|
||||
SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
|
||||
NodeNameResolver $nodeNameResolver
|
||||
) {
|
||||
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string[]> $methodsByPropertyName
|
||||
*/
|
||||
public function replacePropertyFetchesByVariable(Class_ $class, array $methodsByPropertyName): void
|
||||
{
|
||||
foreach ($methodsByPropertyName as $propertyName => $methodNames) {
|
||||
$methodName = $methodNames[0];
|
||||
$classMethod = $class->getMethod($methodName);
|
||||
if ($classMethod === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->simpleCallableNodeTraverser->traverseNodesWithCallable(
|
||||
(array) $classMethod->getStmts(),
|
||||
function (Node $node) use ($propertyName): ?Variable {
|
||||
if (! $node instanceof PropertyFetch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($node, $propertyName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Variable($propertyName);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ namespace Rector\Privatization\Rector\Class_;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Do_;
|
||||
@ -18,7 +17,9 @@ use PhpParser\NodeTraverser;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\PropertyFetchManipulator;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\Util\StaticInstanceOf;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\Privatization\NodeReplacer\PropertyFetchWithVariableReplacer;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
@ -42,10 +43,19 @@ final class ChangeLocalPropertyToVariableRector extends AbstractRector
|
||||
*/
|
||||
private $propertyFetchManipulator;
|
||||
|
||||
public function __construct(ClassManipulator $classManipulator, PropertyFetchManipulator $propertyFetchManipulator)
|
||||
{
|
||||
/**
|
||||
* @var PropertyFetchWithVariableReplacer
|
||||
*/
|
||||
private $propertyFetchWithVariableReplacer;
|
||||
|
||||
public function __construct(
|
||||
ClassManipulator $classManipulator,
|
||||
PropertyFetchManipulator $propertyFetchManipulator,
|
||||
PropertyFetchWithVariableReplacer $propertyFetchWithVariableReplacer
|
||||
) {
|
||||
$this->classManipulator = $classManipulator;
|
||||
$this->propertyFetchManipulator = $propertyFetchManipulator;
|
||||
$this->propertyFetchWithVariableReplacer = $propertyFetchWithVariableReplacer;
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
@ -109,7 +119,7 @@ CODE_SAMPLE
|
||||
unset($propertyUsageByMethods[$propertyName]);
|
||||
}
|
||||
|
||||
$this->replacePropertyFetchesByLocalProperty($node, $propertyUsageByMethods);
|
||||
$this->propertyFetchWithVariableReplacer->replacePropertyFetchesByVariable($node, $propertyUsageByMethods);
|
||||
|
||||
// remove properties
|
||||
foreach ($node->getProperties() as $property) {
|
||||
@ -163,34 +173,6 @@ CODE_SAMPLE
|
||||
return $propertyUsageByMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[][] $propertyUsageByMethods
|
||||
*/
|
||||
private function replacePropertyFetchesByLocalProperty(Class_ $class, array $propertyUsageByMethods): void
|
||||
{
|
||||
foreach ($propertyUsageByMethods as $propertyName => $methodNames) {
|
||||
$methodName = $methodNames[0];
|
||||
$classMethod = $class->getMethod($methodName);
|
||||
if ($classMethod === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->traverseNodesWithCallable((array) $classMethod->getStmts(), function (Node $node) use (
|
||||
$propertyName
|
||||
): ?Variable {
|
||||
if (! $node instanceof PropertyFetch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isName($node, $propertyName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Variable($propertyName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Covers https://github.com/rectorphp/rector/pull/2558#discussion_r363036110
|
||||
*/
|
||||
@ -240,15 +222,7 @@ CODE_SAMPLE
|
||||
|
||||
private function isScopeChangingNode(Node $node): bool
|
||||
{
|
||||
foreach (self::SCOPE_CHANGING_NODE_TYPES as $scopeChangingNode) {
|
||||
if (! is_a($node, $scopeChangingNode, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return StaticInstanceOf::isOneOf($node, self::SCOPE_CHANGING_NODE_TYPES);
|
||||
}
|
||||
|
||||
private function refactorIf(If_ $if, string $privatePropertyName): ?bool
|
||||
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\SymfonyCodeQuality\ApplicationMetadata;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use Rector\Symfony\ServiceMapProvider;
|
||||
use Rector\Symfony\ValueObject\ServiceDefinition;
|
||||
use Rector\Symfony\ValueObject\Tag\EventListenerTag;
|
||||
|
||||
final class ListenerServiceDefinitionProvider
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @see https://regex101.com/r/j6SAga/1
|
||||
*/
|
||||
private const SYMFONY_FAMILY_REGEX = '#^(Symfony|Sensio|Doctrine)\b#';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $areListenerClassesLoaded = false;
|
||||
|
||||
/**
|
||||
* @var ServiceDefinition[][][]
|
||||
*/
|
||||
private $listenerClassesToEvents = [];
|
||||
|
||||
/**
|
||||
* @var ServiceMapProvider
|
||||
*/
|
||||
private $applicationServiceMapProvider;
|
||||
|
||||
public function __construct(ServiceMapProvider $applicationServiceMapProvider)
|
||||
{
|
||||
$this->applicationServiceMapProvider = $applicationServiceMapProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceDefinition[][][]
|
||||
*/
|
||||
public function extract(): array
|
||||
{
|
||||
if ($this->areListenerClassesLoaded) {
|
||||
return $this->listenerClassesToEvents;
|
||||
}
|
||||
|
||||
$serviceMap = $this->applicationServiceMapProvider->provide();
|
||||
$eventListeners = $serviceMap->getServicesByTag('kernel.event_listener');
|
||||
|
||||
foreach ($eventListeners as $eventListener) {
|
||||
// skip Symfony core listeners
|
||||
if (Strings::match((string) $eventListener->getClass(), self::SYMFONY_FAMILY_REGEX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($eventListener->getTags() as $tag) {
|
||||
if (! $tag instanceof EventListenerTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$eventName = $tag->getEvent();
|
||||
$this->listenerClassesToEvents[$eventListener->getClass()][$eventName][] = $eventListener;
|
||||
}
|
||||
}
|
||||
|
||||
$this->areListenerClassesLoaded = true;
|
||||
|
||||
return $this->listenerClassesToEvents;
|
||||
}
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\SymfonyCodeQuality\NodeFactory;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
|
||||
use Rector\Core\Php\PhpVersionProvider;
|
||||
use Rector\Core\PhpParser\Node\Manipulator\VisibilityManipulator;
|
||||
use Rector\Core\PhpParser\Node\NodeFactory;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\Symfony\Contract\Tag\TagInterface;
|
||||
use Rector\Symfony\ValueObject\ServiceDefinition;
|
||||
use Rector\Symfony\ValueObject\Tag;
|
||||
use Rector\Symfony\ValueObject\Tag\EventListenerTag;
|
||||
use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant;
|
||||
|
||||
final class GetSubscriberEventsClassMethodFactory
|
||||
{
|
||||
/**
|
||||
* @var NodeFactory
|
||||
*/
|
||||
private $nodeFactory;
|
||||
|
||||
/**
|
||||
* @var VisibilityManipulator
|
||||
*/
|
||||
private $visibilityManipulator;
|
||||
|
||||
/**
|
||||
* @var PhpVersionProvider
|
||||
*/
|
||||
private $phpVersionProvider;
|
||||
|
||||
/**
|
||||
* @var PhpDocInfoFactory
|
||||
*/
|
||||
private $phpDocInfoFactory;
|
||||
|
||||
public function __construct(
|
||||
NodeFactory $nodeFactory,
|
||||
VisibilityManipulator $visibilityManipulator,
|
||||
PhpVersionProvider $phpVersionProvider,
|
||||
PhpDocInfoFactory $phpDocInfoFactory
|
||||
) {
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
$this->visibilityManipulator = $visibilityManipulator;
|
||||
$this->phpVersionProvider = $phpVersionProvider;
|
||||
$this->phpDocInfoFactory = $phpDocInfoFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, ServiceDefinition[]> $eventsToMethods
|
||||
* @param EventNameToClassAndConstant[] $eventNamesToClassConstants
|
||||
*/
|
||||
public function createFromEventsToMethods(array $eventsToMethods, array $eventNamesToClassConstants): ClassMethod
|
||||
{
|
||||
$getSubscribersClassMethod = $this->nodeFactory->createPublicMethod('getSubscribedEvents');
|
||||
|
||||
$eventsToMethodsArray = new Array_();
|
||||
|
||||
$this->visibilityManipulator->makeStatic($getSubscribersClassMethod);
|
||||
|
||||
foreach ($eventsToMethods as $eventName => $methodNamesWithPriorities) {
|
||||
$eventNameExpr = $this->createEventName($eventName, $eventNamesToClassConstants);
|
||||
|
||||
if (count($methodNamesWithPriorities) === 1) {
|
||||
$this->createSingleMethod(
|
||||
$methodNamesWithPriorities,
|
||||
$eventName,
|
||||
$eventNameExpr,
|
||||
$eventsToMethodsArray
|
||||
);
|
||||
} else {
|
||||
$this->createMultipleMethods(
|
||||
$methodNamesWithPriorities,
|
||||
$eventNameExpr,
|
||||
$eventsToMethodsArray,
|
||||
$eventName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$getSubscribersClassMethod->stmts[] = new Return_($eventsToMethodsArray);
|
||||
$this->decorateClassMethodWithReturnType($getSubscribersClassMethod);
|
||||
|
||||
return $getSubscribersClassMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassConstFetch|String_ $expr
|
||||
* @param ServiceDefinition[] $methodNamesWithPriorities
|
||||
*/
|
||||
private function createSingleMethod(
|
||||
array $methodNamesWithPriorities,
|
||||
string $eventName,
|
||||
Expr $expr,
|
||||
Array_ $eventsToMethodsArray
|
||||
): void {
|
||||
|
||||
/** @var EventListenerTag[]|Tag[] $eventTags */
|
||||
$eventTags = $methodNamesWithPriorities[0]->getTags();
|
||||
foreach ($eventTags as $eventTag) {
|
||||
if ($eventTag instanceof EventListenerTag && $eventTag->getEvent() === $eventName) {
|
||||
$methodName = $eventTag->getMethod();
|
||||
$priority = $eventTag->getPriority();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($methodName, $priority)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($priority !== 0) {
|
||||
$methodNameWithPriorityArray = new Array_();
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($methodName));
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber((int) $priority));
|
||||
|
||||
$eventsToMethodsArray->items[] = new ArrayItem($methodNameWithPriorityArray, $expr);
|
||||
} else {
|
||||
$eventsToMethodsArray->items[] = new ArrayItem(new String_($methodName), $expr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassConstFetch|String_ $expr
|
||||
* @param ServiceDefinition[] $methodNamesWithPriorities
|
||||
*/
|
||||
private function createMultipleMethods(
|
||||
array $methodNamesWithPriorities,
|
||||
Expr $expr,
|
||||
Array_ $eventsToMethodsArray,
|
||||
string $eventName
|
||||
): void {
|
||||
$eventItems = [];
|
||||
$alreadyUsedTags = [];
|
||||
|
||||
foreach ($methodNamesWithPriorities as $methodNamesWithPriority) {
|
||||
foreach ($methodNamesWithPriority->getTags() as $tag) {
|
||||
if (! $tag instanceof EventListenerTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->shouldSkip($eventName, $tag, $alreadyUsedTags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$eventItems[] = $this->createEventItem($tag);
|
||||
|
||||
$alreadyUsedTags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$multipleMethodsArray = new Array_($eventItems);
|
||||
|
||||
$eventsToMethodsArray->items[] = new ArrayItem($multipleMethodsArray, $expr);
|
||||
}
|
||||
|
||||
private function decorateClassMethodWithReturnType(ClassMethod $classMethod): void
|
||||
{
|
||||
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
|
||||
$classMethod->returnType = new Identifier('array');
|
||||
}
|
||||
|
||||
$returnType = new ArrayType(new MixedType(), new MixedType(true));
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
|
||||
$phpDocInfo->changeReturnType($returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TagInterface[] $alreadyUsedTags
|
||||
*/
|
||||
private function shouldSkip(string $eventName, EventListenerTag $eventListenerTag, array $alreadyUsedTags): bool
|
||||
{
|
||||
if ($eventName !== $eventListenerTag->getEvent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($eventListenerTag, $alreadyUsedTags, true);
|
||||
}
|
||||
|
||||
private function createEventItem(EventListenerTag $eventListenerTag): ArrayItem
|
||||
{
|
||||
if ($eventListenerTag->getPriority() !== 0) {
|
||||
$methodNameWithPriorityArray = new Array_();
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($eventListenerTag->getMethod()));
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber($eventListenerTag->getPriority()));
|
||||
|
||||
return new ArrayItem($methodNameWithPriorityArray);
|
||||
}
|
||||
|
||||
return new ArrayItem(new String_($eventListenerTag->getMethod()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EventNameToClassAndConstant[] $eventNamesToClassConstants
|
||||
* @return String_|ClassConstFetch
|
||||
*/
|
||||
private function createEventName(string $eventName, array $eventNamesToClassConstants): Node
|
||||
{
|
||||
if (class_exists($eventName)) {
|
||||
return $this->nodeFactory->createClassConstReference($eventName);
|
||||
}
|
||||
|
||||
// is string a that could be caught in constant, e.g. KernelEvents?
|
||||
foreach ($eventNamesToClassConstants as $eventNameToClassConstant) {
|
||||
if ($eventNameToClassConstant->getEventName() !== $eventName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $this->nodeFactory->createClassConstFetch(
|
||||
$eventNameToClassConstant->getEventClass(),
|
||||
$eventNameToClassConstant->getEventConstant()
|
||||
);
|
||||
}
|
||||
|
||||
return new String_($eventName);
|
||||
}
|
||||
}
|
@ -6,26 +6,13 @@ namespace Rector\SymfonyCodeQuality\Rector\Class_;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\Symfony\Contract\Tag\TagInterface;
|
||||
use Rector\Symfony\ServiceMapProvider;
|
||||
use Rector\Symfony\ValueObject\ServiceDefinition;
|
||||
use Rector\Symfony\ValueObject\Tag;
|
||||
use Rector\Symfony\ValueObject\Tag\EventListenerTag;
|
||||
use Rector\SymfonyCodeQuality\ApplicationMetadata\ListenerServiceDefinitionProvider;
|
||||
use Rector\SymfonyCodeQuality\NodeFactory\GetSubscriberEventsClassMethodFactory;
|
||||
use Rector\SymfonyCodeQuality\ValueObject\EventNameToClassAndConstant;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
@ -56,36 +43,25 @@ final class EventListenerToEventSubscriberRector extends AbstractRector
|
||||
*/
|
||||
private const LISTENER_MATCH_REGEX = '#^(.*?)(Listener)?$#';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @see https://regex101.com/r/j6SAga/1
|
||||
*/
|
||||
private const SYMFONY_FAMILY_REGEX = '#^(Symfony|Sensio|Doctrine)\b#';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $areListenerClassesLoaded = false;
|
||||
|
||||
/**
|
||||
* @var EventNameToClassAndConstant[]
|
||||
*/
|
||||
private $eventNamesToClassConstants = [];
|
||||
|
||||
/**
|
||||
* @var ServiceDefinition[][][]
|
||||
* @var ListenerServiceDefinitionProvider
|
||||
*/
|
||||
private $listenerClassesToEvents = [];
|
||||
private $listenerServiceDefinitionProvider;
|
||||
|
||||
/**
|
||||
* @var ServiceMapProvider
|
||||
* @var GetSubscriberEventsClassMethodFactory
|
||||
*/
|
||||
private $applicationServiceMapProvider;
|
||||
|
||||
public function __construct(ServiceMapProvider $applicationServiceMapProvider)
|
||||
{
|
||||
$this->applicationServiceMapProvider = $applicationServiceMapProvider;
|
||||
private $getSubscriberEventsClassMethodFactory;
|
||||
|
||||
public function __construct(
|
||||
ListenerServiceDefinitionProvider $listenerServiceDefinitionProvider,
|
||||
GetSubscriberEventsClassMethodFactory $getSubscriberEventsClassMethodFactory
|
||||
) {
|
||||
$this->eventNamesToClassConstants = [
|
||||
// kernel events
|
||||
new EventNameToClassAndConstant('kernel.request', self::KERNEL_EVENTS_CLASS, 'REQUEST'),
|
||||
@ -105,6 +81,8 @@ final class EventListenerToEventSubscriberRector extends AbstractRector
|
||||
new EventNameToClassAndConstant('console.terminate', self::CONSOLE_EVENTS_CLASS, 'TERMINATE'),
|
||||
new EventNameToClassAndConstant('console.error', self::CONSOLE_EVENTS_CLASS, 'ERROR'),
|
||||
];
|
||||
$this->listenerServiceDefinitionProvider = $listenerServiceDefinitionProvider;
|
||||
$this->getSubscriberEventsClassMethodFactory = $getSubscriberEventsClassMethodFactory;
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
@ -175,7 +153,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
// there must be event dispatcher in the application
|
||||
$listenerClassesToEventsToMethods = $this->getListenerClassesToEventsToMethods();
|
||||
$listenerClassesToEventsToMethods = $this->listenerServiceDefinitionProvider->extract();
|
||||
if ($listenerClassesToEventsToMethods === []) {
|
||||
return null;
|
||||
}
|
||||
@ -200,40 +178,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceDefinition[][][]
|
||||
*/
|
||||
private function getListenerClassesToEventsToMethods(): array
|
||||
{
|
||||
if ($this->areListenerClassesLoaded) {
|
||||
return $this->listenerClassesToEvents;
|
||||
}
|
||||
|
||||
$serviceMap = $this->applicationServiceMapProvider->provide();
|
||||
$eventListeners = $serviceMap->getServicesByTag('kernel.event_listener');
|
||||
|
||||
foreach ($eventListeners as $eventListener) {
|
||||
// skip Symfony core listeners
|
||||
if (Strings::match((string) $eventListener->getClass(), self::SYMFONY_FAMILY_REGEX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($eventListener->getTags() as $tag) {
|
||||
if (! $tag instanceof EventListenerTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$eventName = $tag->getEvent();
|
||||
$this->listenerClassesToEvents[$eventListener->getClass()][$eventName][] = $eventListener;
|
||||
}
|
||||
}
|
||||
|
||||
$this->areListenerClassesLoaded = true;
|
||||
|
||||
return $this->listenerClassesToEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $eventsToMethods
|
||||
* @param array<string, ServiceDefinition[]> $eventsToMethods
|
||||
*/
|
||||
private function changeListenerToSubscriberWithMethods(Class_ $class, array $eventsToMethods): Class_
|
||||
{
|
||||
@ -246,176 +191,12 @@ CODE_SAMPLE
|
||||
|
||||
$class->name = new Identifier($classShortName . 'EventSubscriber');
|
||||
|
||||
$classMethod = $this->createGetSubscribedEventsClassMethod($eventsToMethods);
|
||||
$classMethod = $this->getSubscriberEventsClassMethodFactory->createFromEventsToMethods(
|
||||
$eventsToMethods,
|
||||
$this->eventNamesToClassConstants
|
||||
);
|
||||
$class->stmts[] = $classMethod;
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[][] $eventsToMethods
|
||||
*/
|
||||
private function createGetSubscribedEventsClassMethod(array $eventsToMethods): ClassMethod
|
||||
{
|
||||
$getSubscribersClassMethod = $this->nodeFactory->createPublicMethod('getSubscribedEvents');
|
||||
|
||||
$eventsToMethodsArray = new Array_();
|
||||
|
||||
$this->makeStatic($getSubscribersClassMethod);
|
||||
|
||||
foreach ($eventsToMethods as $eventName => $methodNamesWithPriorities) {
|
||||
$eventNameExpr = $this->createEventName($eventName);
|
||||
|
||||
if (count($methodNamesWithPriorities) === 1) {
|
||||
$this->createSingleMethod(
|
||||
$methodNamesWithPriorities,
|
||||
$eventName,
|
||||
$eventNameExpr,
|
||||
$eventsToMethodsArray
|
||||
);
|
||||
} else {
|
||||
$this->createMultipleMethods(
|
||||
$methodNamesWithPriorities,
|
||||
$eventNameExpr,
|
||||
$eventsToMethodsArray,
|
||||
$eventName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$getSubscribersClassMethod->stmts[] = new Return_($eventsToMethodsArray);
|
||||
$this->decorateClassMethodWithReturnType($getSubscribersClassMethod);
|
||||
|
||||
return $getSubscribersClassMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String_|ClassConstFetch
|
||||
*/
|
||||
private function createEventName(string $eventName): Node
|
||||
{
|
||||
if (class_exists($eventName)) {
|
||||
return $this->createClassConstReference($eventName);
|
||||
}
|
||||
|
||||
// is string a that could be caught in constant, e.g. KernelEvents?
|
||||
foreach ($this->eventNamesToClassConstants as $eventNameToClassConstant) {
|
||||
if ($eventNameToClassConstant->getEventName() !== $eventName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $this->createClassConstFetch(
|
||||
$eventNameToClassConstant->getEventClass(),
|
||||
$eventNameToClassConstant->getEventConstant()
|
||||
);
|
||||
}
|
||||
|
||||
return new String_($eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassConstFetch|String_ $expr
|
||||
* @param ServiceDefinition[] $methodNamesWithPriorities
|
||||
*/
|
||||
private function createSingleMethod(
|
||||
array $methodNamesWithPriorities,
|
||||
string $eventName,
|
||||
Expr $expr,
|
||||
Array_ $eventsToMethodsArray
|
||||
): void {
|
||||
|
||||
/** @var EventListenerTag[]|Tag[] $eventTags */
|
||||
$eventTags = $methodNamesWithPriorities[0]->getTags();
|
||||
foreach ($eventTags as $eventTag) {
|
||||
if ($eventTag instanceof EventListenerTag && $eventTag->getEvent() === $eventName) {
|
||||
$methodName = $eventTag->getMethod();
|
||||
$priority = $eventTag->getPriority();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($methodName, $priority)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($priority !== 0) {
|
||||
$methodNameWithPriorityArray = new Array_();
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($methodName));
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber((int) $priority));
|
||||
|
||||
$eventsToMethodsArray->items[] = new ArrayItem($methodNameWithPriorityArray, $expr);
|
||||
} else {
|
||||
$eventsToMethodsArray->items[] = new ArrayItem(new String_($methodName), $expr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassConstFetch|String_ $expr
|
||||
* @param ServiceDefinition[] $methodNamesWithPriorities
|
||||
*/
|
||||
private function createMultipleMethods(
|
||||
array $methodNamesWithPriorities,
|
||||
Expr $expr,
|
||||
Array_ $eventsToMethodsArray,
|
||||
string $eventName
|
||||
): void {
|
||||
$eventItems = [];
|
||||
$alreadyUsedTags = [];
|
||||
|
||||
foreach ($methodNamesWithPriorities as $methodNamesWithPriority) {
|
||||
foreach ($methodNamesWithPriority->getTags() as $tag) {
|
||||
if (! $tag instanceof EventListenerTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->shouldSkip($eventName, $tag, $alreadyUsedTags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$eventItems[] = $this->createEventItem($tag);
|
||||
|
||||
$alreadyUsedTags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$multipleMethodsArray = new Array_($eventItems);
|
||||
|
||||
$eventsToMethodsArray->items[] = new ArrayItem($multipleMethodsArray, $expr);
|
||||
}
|
||||
|
||||
private function decorateClassMethodWithReturnType(ClassMethod $classMethod): void
|
||||
{
|
||||
if ($this->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
|
||||
$classMethod->returnType = new Identifier('array');
|
||||
}
|
||||
|
||||
$returnType = new ArrayType(new MixedType(), new MixedType(true));
|
||||
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
|
||||
$phpDocInfo->changeReturnType($returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TagInterface[] $alreadyUsedTags
|
||||
*/
|
||||
private function shouldSkip(string $eventName, EventListenerTag $eventListenerTag, array $alreadyUsedTags): bool
|
||||
{
|
||||
if ($eventName !== $eventListenerTag->getEvent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($eventListenerTag, $alreadyUsedTags, true);
|
||||
}
|
||||
|
||||
private function createEventItem(EventListenerTag $eventListenerTag): ArrayItem
|
||||
{
|
||||
if ($eventListenerTag->getPriority() !== 0) {
|
||||
$methodNameWithPriorityArray = new Array_();
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($eventListenerTag->getMethod()));
|
||||
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber($eventListenerTag->getPriority()));
|
||||
|
||||
return new ArrayItem($methodNameWithPriorityArray);
|
||||
}
|
||||
|
||||
return new ArrayItem(new String_($eventListenerTag->getMethod()));
|
||||
}
|
||||
}
|
||||
|
@ -462,15 +462,15 @@ final class NodeFactory
|
||||
return $uses;
|
||||
}
|
||||
|
||||
public function createStaticCall(string $class, string $method): StaticCall
|
||||
/**
|
||||
* @param Node[] $args
|
||||
*/
|
||||
public function createStaticCall(string $class, string $method, array $args = []): StaticCall
|
||||
{
|
||||
if (in_array($class, self::REFERENCES, true)) {
|
||||
$class = new Name($class);
|
||||
} else {
|
||||
$class = new FullyQualified($class);
|
||||
}
|
||||
|
||||
return new StaticCall($class, $method);
|
||||
$class = $this->createClassPart($class);
|
||||
$staticCall = new StaticCall($class, $method);
|
||||
$staticCall->args = $this->createArgs($args);
|
||||
return $staticCall;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,4 +635,13 @@ final class NodeFactory
|
||||
|
||||
return $arrayItem;
|
||||
}
|
||||
|
||||
private function createClassPart(string $class): Name
|
||||
{
|
||||
if (in_array($class, self::REFERENCES, true)) {
|
||||
return new Name($class);
|
||||
}
|
||||
|
||||
return new FullyQualified($class);
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ services:
|
||||
arguments:
|
||||
maxClassCognitiveComplexity: 50
|
||||
limitsByTypes:
|
||||
Rector\Core\Rector\AbstractRector: 40
|
||||
Rector\Core\Rector\AbstractRector: 35
|
||||
Symfony\Component\Console\Command\Command: 40
|
||||
PHPStan\Rule\Rule: 30
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user