mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-25 20:32:35 +00:00
fix CallableThisArrayToAnonymousFunctionRector for invalid array items
This commit is contained in:
parent
de331920c8
commit
adc50652a0
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Rector\CodeQuality\Rector\Array_;
|
||||
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
|
@ -93,19 +95,21 @@ CODE_SAMPLE
|
|||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if (count($node->items) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// is callable?
|
||||
// can be totally empty, e.g [, $value]
|
||||
if ($node->items[0] === null) {
|
||||
if ($this->shouldSkipArray($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$objectVariable = $node->items[0]->value;
|
||||
if (! $objectVariable instanceof Variable && ! $objectVariable instanceof PropertyFetch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classMethod = $this->matchCallableMethod($objectVariable, $node->items[1]->value);
|
||||
$methodName = $node->items[1]->value;
|
||||
if (! $methodName instanceof String_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classMethod = $this->matchCallableMethod($objectVariable, $methodName);
|
||||
if ($classMethod === null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -145,7 +149,10 @@ CODE_SAMPLE
|
|||
return $args;
|
||||
}
|
||||
|
||||
private function matchCallableMethod(Expr $objectExpr, Expr $methodExpr): ?ClassMethod
|
||||
/**
|
||||
* @param Variable|PropertyFetch $objectExpr
|
||||
*/
|
||||
private function matchCallableMethod(Expr $objectExpr, String_ $methodExpr): ?ClassMethod
|
||||
{
|
||||
$methodName = $this->getValue($methodExpr);
|
||||
|
||||
|
@ -173,4 +180,15 @@ CODE_SAMPLE
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function shouldSkipArray(Array_ $array): bool
|
||||
{
|
||||
// callback is exactly "[$two, 'items']"
|
||||
if (count($array->items) !== 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// can be totally empty in case of "[, $value]"
|
||||
return $array->items[0] === null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@ final class CallableThisArrayToAnonymousFunctionRectorTest extends AbstractRecto
|
|||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/skip.php.inc',
|
||||
__DIR__ . '/Fixture/another_class.php.inc',
|
||||
__DIR__ . '/Fixture/skip_too.php.inc',
|
||||
__DIR__ . '/Fixture/skip_another_class.php.inc',
|
||||
__DIR__ . '/Fixture/skip_empty_first_array.php.inc',
|
||||
__DIR__ . '/Fixture/skip_as_well.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\ConfiguredCodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
final class PseudoNamespaceToNamespaceRector
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $newNamespace;
|
||||
|
||||
/**
|
||||
* @var string[][]|null[]
|
||||
*/
|
||||
private $namespacePrefixesWithExcludedClasses = [];
|
||||
|
||||
/**
|
||||
* @var ClassManipulator
|
||||
*/
|
||||
private $classManipulator;
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @param string[][]|null[] $namespacePrefixesWithExcludedClasses
|
||||
*/
|
||||
public function __construct(
|
||||
ClassManipulator $classManipulator,
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
array $namespacePrefixesWithExcludedClasses = []
|
||||
) {
|
||||
$this->classManipulator = $classManipulator;
|
||||
$this->namespacePrefixesWithExcludedClasses = $namespacePrefixesWithExcludedClasses;
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Replaces defined Pseudo_Namespaces by Namespace\Ones.', [
|
||||
new ConfiguredCodeSample(
|
||||
'$someService = new Some_Object;',
|
||||
'$someService = new Some\Object;',
|
||||
[
|
||||
['Some_' => []],
|
||||
]
|
||||
),
|
||||
new ConfiguredCodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
/** @var Some_Object $someService */
|
||||
$someService = new Some_Object;
|
||||
$someClassToKeep = new Some_Class_To_Keep;
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
/** @var Some\Object $someService */
|
||||
$someService = new Some\Object;
|
||||
$someClassToKeep = new Some_Class_To_Keep;
|
||||
CODE_SAMPLE
|
||||
,
|
||||
[
|
||||
['Some_' => ['Some_Class_To_Keep']],
|
||||
]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
// property, method
|
||||
return [Name::class, Identifier::class, Property::class, FunctionLike::class, Expression::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Name|Identifier|Property|FunctionLike $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
// replace on @var/@param/@return/@throws
|
||||
foreach ($this->namespacePrefixesWithExcludedClasses as $namespacePrefix => $excludedClasses) {
|
||||
$this->docBlockManipulator->changeUnderscoreType($node, $namespacePrefix, $excludedClasses);
|
||||
}
|
||||
|
||||
if ($node instanceof Name || $node instanceof Identifier) {
|
||||
return $this->processNameOrIdentifier($node);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Stmt[] $nodes
|
||||
* @return Node[]
|
||||
*/
|
||||
public function afterTraverse(array $nodes): array
|
||||
{
|
||||
if ($this->newNamespace === null) {
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
$namespaceNode = new Namespace_(new Name($this->newNamespace));
|
||||
foreach ($nodes as $key => $node) {
|
||||
if ($node instanceof Class_) {
|
||||
$nodes = $this->classManipulator->insertBeforeAndFollowWithNewline($nodes, $namespaceNode, $key);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->newNamespace = null;
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
private function processName(Name $name): Name
|
||||
{
|
||||
$nodeName = $this->getName($name);
|
||||
|
||||
if ($nodeName !== null) {
|
||||
$name->parts = explode('_', $nodeName);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function processIdentifier(Identifier $identifier): ?Identifier
|
||||
{
|
||||
$parentNode = $identifier->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if (! $parentNode instanceof Class_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = $this->getName($identifier);
|
||||
if ($name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newNameParts = explode('_', $name);
|
||||
$lastNewNamePart = $newNameParts[count($newNameParts) - 1];
|
||||
|
||||
$namespaceParts = $newNameParts;
|
||||
array_pop($namespaceParts);
|
||||
|
||||
$newNamespace = implode('\\', $namespaceParts);
|
||||
if ($this->newNamespace !== null && $this->newNamespace !== $newNamespace) {
|
||||
throw new ShouldNotHappenException('There cannot be 2 different namespaces in one file');
|
||||
}
|
||||
|
||||
$this->newNamespace = $newNamespace;
|
||||
|
||||
$identifier->name = $lastNewNamePart;
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Name|Identifier $node
|
||||
* @return Name|Identifier
|
||||
*/
|
||||
private function processNameOrIdentifier(Node $node): ?Node
|
||||
{
|
||||
// no name → skip
|
||||
if ($node->toString() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->namespacePrefixesWithExcludedClasses as $namespacePrefix => $excludedClasses) {
|
||||
if (! $this->isName($node, $namespacePrefix . '*')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($excludedClasses) && $this->isNames($node, $excludedClasses)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($node instanceof Name) {
|
||||
return $this->processName($node);
|
||||
}
|
||||
|
||||
return $this->processIdentifier($node);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
namespace Rector\CodeQuality\Tests\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Foreach_;
|
||||
|
||||
final class ForeachToInArrayRector
|
||||
{
|
||||
public function refactor(Node $node)
|
||||
public function refactor($node)
|
||||
{
|
||||
[, $comparedNode] = $matchedNodes;
|
||||
[, $comparedNode] = $node;
|
||||
}
|
||||
}
|
|
@ -334,7 +334,7 @@ CODE_SAMPLE
|
|||
private function isCurrentNamespace(string $namespaceName, string $newUseStatement): bool
|
||||
{
|
||||
$afterCurrentNamespace = Strings::after($newUseStatement, $namespaceName . '\\');
|
||||
if ($afterCurrentNamespace === false) {
|
||||
if (! $afterCurrentNamespace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user