Merge pull request #662 from rectorphp/php-split-string

[PHP] string split
This commit is contained in:
Tomáš Votruba 2018-10-07 20:01:11 +08:00 committed by GitHub
commit a89b6b3824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 177 additions and 22 deletions

View File

@ -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: ~

View File

@ -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'

View 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;
}
}

View File

@ -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()) {

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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');

View File

@ -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';
}
}

View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
$string = 'abcde';
list($foo) = $string;
$array = ['abcde'];
list($bar) = $array;
list($foo) = 'abcde';

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\List_\ListSplitStringRector: ~

View File

@ -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