mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-19 06:18:07 +01:00
Merge pull request #662 from rectorphp/php-split-string
[PHP] string split
This commit is contained in:
commit
a89b6b3824
@ -4,3 +4,4 @@ services:
|
||||
Rector\Php\Rector\FuncCall\RandomFunctionRector: ~
|
||||
Rector\Php\Rector\FunctionLike\ExceptionHandlerTypehintRector: ~
|
||||
Rector\Php\Rector\FuncCall\MultiDirnameRector: ~
|
||||
Rector\Php\Rector\List_\ListSplitStringRector: ~
|
||||
|
4
ecs.yml
4
ecs.yml
@ -114,3 +114,7 @@ parameters:
|
||||
SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint:
|
||||
# 3rd party parent code
|
||||
- 'src/DependencyInjection/Loader/TolerantRectorYamlFileLoader.php'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\Debug\CommentedOutCodeSniff.Found:
|
||||
# notes
|
||||
- 'packages/Php/src/Rector/Each/ListEachRector.php'
|
||||
|
24
packages/NodeTypeResolver/src/NodeTypeAnalyzer.php
Normal file
24
packages/NodeTypeResolver/src/NodeTypeAnalyzer.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Type\StringType;
|
||||
use Rector\NodeTypeResolver\Node\Attribute;
|
||||
|
||||
final class NodeTypeAnalyzer
|
||||
{
|
||||
public function isStringType(Node $node): bool
|
||||
{
|
||||
if (! $node instanceof Expr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Scope $nodeScope */
|
||||
$nodeScope = $node->getAttribute(Attribute::SCOPE);
|
||||
|
||||
return $nodeScope->getType($node) instanceof StringType;
|
||||
}
|
||||
}
|
@ -46,7 +46,6 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface
|
||||
{
|
||||
/** @var Scope $nodeScope */
|
||||
$nodeScope = $variableNode->getAttribute(Attribute::SCOPE);
|
||||
|
||||
$variableName = (string) $variableNode->name;
|
||||
|
||||
if ($nodeScope->hasVariableType($variableName) === TrinaryLogic::createYes()) {
|
||||
|
@ -52,20 +52,12 @@ CODE_SAMPLE
|
||||
*/
|
||||
public function refactor(Node $assignNode): ?Node
|
||||
{
|
||||
if (! $this->isListToEachAssign($assignNode)) {
|
||||
return $assignNode;
|
||||
}
|
||||
|
||||
// assign should be top level, e.g. not in a while loop
|
||||
if (! $assignNode->getAttribute(Attribute::PARENT_NODE) instanceof Expression) {
|
||||
if ($this->shouldSkip($assignNode)) {
|
||||
return $assignNode;
|
||||
}
|
||||
|
||||
/** @var List_ $listNode */
|
||||
$listNode = $assignNode->var;
|
||||
if (count($listNode->items) !== 2) {
|
||||
return $listNode;
|
||||
}
|
||||
|
||||
/** @var FuncCall $eachFuncCall */
|
||||
$eachFuncCall = $assignNode->expr;
|
||||
@ -90,21 +82,17 @@ CODE_SAMPLE
|
||||
// $key = key($values);
|
||||
// $value = current($values);
|
||||
// next($values); - only inside a loop
|
||||
if ($listNode->items[0] && $listNode->items[1]) {
|
||||
$currentFuncCall = $this->createFuncCallWithNameAndArgs('current', $eachFuncCall->args);
|
||||
$assignCurrentNode = new Assign($listNode->items[1]->value, $currentFuncCall);
|
||||
$this->addNodeAfterNode($assignCurrentNode, $assignNode);
|
||||
$currentFuncCall = $this->createFuncCallWithNameAndArgs('current', $eachFuncCall->args);
|
||||
$assignCurrentNode = new Assign($listNode->items[1]->value, $currentFuncCall);
|
||||
$this->addNodeAfterNode($assignCurrentNode, $assignNode);
|
||||
|
||||
if ($this->isInsideDoWhile($assignNode)) {
|
||||
$nextFuncCall = $this->createFuncCallWithNameAndArgs('next', $eachFuncCall->args);
|
||||
$this->addNodeAfterNode($nextFuncCall, $assignNode);
|
||||
}
|
||||
|
||||
$keyFuncCall = $this->createFuncCallWithNameAndArgs('key', $eachFuncCall->args);
|
||||
return new Assign($listNode->items[0]->value, $keyFuncCall);
|
||||
if ($this->isInsideDoWhile($assignNode)) {
|
||||
$nextFuncCall = $this->createFuncCallWithNameAndArgs('next', $eachFuncCall->args);
|
||||
$this->addNodeAfterNode($nextFuncCall, $assignNode);
|
||||
}
|
||||
|
||||
return $assignNode;
|
||||
$keyFuncCall = $this->createFuncCallWithNameAndArgs('key', $eachFuncCall->args);
|
||||
return new Assign($listNode->items[0]->value, $keyFuncCall);
|
||||
}
|
||||
|
||||
private function isListToEachAssign(Assign $assignNode): bool
|
||||
@ -138,4 +126,26 @@ CODE_SAMPLE
|
||||
|
||||
return $parentParentNode instanceof Do_;
|
||||
}
|
||||
|
||||
private function shouldSkip(Assign $assignNode): bool
|
||||
{
|
||||
if (! $this->isListToEachAssign($assignNode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// assign should be top level, e.g. not in a while loop
|
||||
if (! $assignNode->getAttribute(Attribute::PARENT_NODE) instanceof Expression) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var List_ $listNode */
|
||||
$listNode = $assignNode->var;
|
||||
|
||||
if (count($listNode->items) !== 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// empty list → cannot handle
|
||||
return $listNode->items[0] === null && $listNode->items[1] === null;
|
||||
}
|
||||
}
|
||||
|
66
packages/Php/src/Rector/List_/ListSplitStringRector.php
Normal file
66
packages/Php/src/Rector/List_/ListSplitStringRector.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Rector\List_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\List_;
|
||||
use PhpParser\Node\Name;
|
||||
use Rector\NodeTypeResolver\NodeTypeAnalyzer;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @source http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.variable-handling.list
|
||||
*
|
||||
* @see https://stackoverflow.com/a/47965344/1348344
|
||||
*/
|
||||
final class ListSplitStringRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var NodeTypeAnalyzer
|
||||
*/
|
||||
private $nodeTypeAnalyzer;
|
||||
|
||||
public function __construct(NodeTypeAnalyzer $nodeTypeAnalyzer)
|
||||
{
|
||||
$this->nodeTypeAnalyzer = $nodeTypeAnalyzer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition(
|
||||
'list() cannot split string directly anymore, use str_split()',
|
||||
[new CodeSample('list($foo) = "string";', 'list($foo) = str_split("string");')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Assign::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Assign $assignNode
|
||||
*/
|
||||
public function refactor(Node $assignNode): ?Node
|
||||
{
|
||||
if (! $assignNode->var instanceof List_) {
|
||||
return $assignNode;
|
||||
}
|
||||
|
||||
if (! $this->nodeTypeAnalyzer->isStringType($assignNode->expr)) {
|
||||
return $assignNode;
|
||||
}
|
||||
|
||||
$assignNode->expr = new FuncCall(new Name('str_split'), [new Arg($assignNode->expr)]);
|
||||
|
||||
return $assignNode;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$string = 'abcde';
|
||||
list($foo) = str_split($string);
|
||||
|
||||
$array = ['abcde'];
|
||||
list($bar) = $array;
|
||||
|
||||
list($foo) = str_split('abcde');
|
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php\Tests\Rector\List_\ListSplitStringRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Rector\Php\Rector\List_\ListSplitStringRector
|
||||
*/
|
||||
final class ListSplitStringRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideWrongToFixedFiles()
|
||||
*/
|
||||
public function test(string $wrong, string $fixed): void
|
||||
{
|
||||
$this->doTestFileMatchesExpectedContent($wrong, $fixed);
|
||||
}
|
||||
|
||||
public function provideWrongToFixedFiles(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc'];
|
||||
}
|
||||
|
||||
protected function provideConfig(): string
|
||||
{
|
||||
return __DIR__ . '/config.yml';
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$string = 'abcde';
|
||||
list($foo) = $string;
|
||||
|
||||
$array = ['abcde'];
|
||||
list($bar) = $array;
|
||||
|
||||
list($foo) = 'abcde';
|
@ -0,0 +1,2 @@
|
||||
services:
|
||||
Rector\Php\Rector\List_\ListSplitStringRector: ~
|
@ -73,6 +73,7 @@ parameters:
|
||||
|
||||
# known values
|
||||
- '#Access to an undefined property PHPStan\\PhpDocParser\\Ast\\Node::\$name#' # 2
|
||||
- '#Cannot access property \$value on PhpParser\\Node\\Expr\\ArrayItem\|null#'
|
||||
|
||||
- '#Method Rector\\Node\\NodeFactory::createNullConstant\(\) should return PhpParser\\Node\\Expr\\ConstFetch but returns PhpParser\\Node\\Expr#' # 1
|
||||
- '#Method Rector\\Node\\NodeFactory::createNamespace\(\) should return PhpParser\\Node\\Stmt\\Namespace_ but returns PhpParser\\Node#' # 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user