[PHP] Add BarewordStringRector

This commit is contained in:
Tomas Votruba 2018-10-16 20:43:52 +08:00
parent ee61b7ed28
commit 4faa1425c8
13 changed files with 213 additions and 2 deletions

View File

@ -2,3 +2,4 @@ services:
Rector\Php\Rector\While_\WhileEachToForeachRector: ~
Rector\Php\Rector\Each\ListEachRector: ~
Rector\Php\Rector\Unset_\UnsetCastRector: ~
Rector\Php\Rector\String_\BarewordStringRector: ~

View File

@ -106,6 +106,7 @@ parameters:
- '*NodeVisitor.php'
- '*CompilerPass.php'
- 'packages/Php/src/Rector/Unset_/UnsetCastRector.php'
- 'packages/Php/src/Rector/String_/BarewordStringRector.php'
# array type check
- 'src/RectorDefinition/RectorDefinition.php'

View File

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\FileSystem;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class CurrentFileInfoProvider
{
/**
* @var SmartFileInfo|null
*/
private $smartFileInfo;
public function setCurrentFileInfo(SmartFileInfo $smartFileInfo): void
{
$this->smartFileInfo = $smartFileInfo;
}
public function getSmartFileInfo(): ?SmartFileInfo
{
return $this->smartFileInfo;
}
}

View File

@ -89,4 +89,9 @@ final class Attribute
* @var string
*/
public const CURRENT_EXPRESSION = 'currentExpression';
/**
* @var string
*/
public const FILE_INFO = 'fileInfo';
}

View File

@ -8,6 +8,7 @@ use PhpParser\NodeVisitor\CloningVisitor;
use PhpParser\NodeVisitor\NameResolver;
use Rector\NodeTypeResolver\NodeVisitor\ClassAndMethodNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\ExpressionNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\FileInfoNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\NamespaceNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\ParentAndNextNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeScopeResolver;
@ -44,13 +45,19 @@ final class NodeScopeAndMetadataDecorator
*/
private $expressionNodeVisitor;
/**
* @var FileInfoNodeVisitor
*/
private $fileInfoNodeVisitor;
public function __construct(
NodeScopeResolver $nodeScopeResolver,
ParentAndNextNodeVisitor $parentAndNextNodeVisitor,
CloningVisitor $cloningVisitor,
ClassAndMethodNodeVisitor $classAndMethodNodeVisitor,
NamespaceNodeVisitor $namespaceNodeVisitor,
ExpressionNodeVisitor $expressionNodeVisitor
ExpressionNodeVisitor $expressionNodeVisitor,
FileInfoNodeVisitor $fileInfoNodeVisitor
) {
$this->nodeScopeResolver = $nodeScopeResolver;
$this->parentAndNextNodeVisitor = $parentAndNextNodeVisitor;
@ -58,6 +65,7 @@ final class NodeScopeAndMetadataDecorator
$this->classAndMethodNodeVisitor = $classAndMethodNodeVisitor;
$this->namespaceNodeVisitor = $namespaceNodeVisitor;
$this->expressionNodeVisitor = $expressionNodeVisitor;
$this->fileInfoNodeVisitor = $fileInfoNodeVisitor;
}
/**
@ -86,6 +94,7 @@ final class NodeScopeAndMetadataDecorator
$nodeTraverser->addVisitor($this->classAndMethodNodeVisitor);
$nodeTraverser->addVisitor($this->namespaceNodeVisitor);
$nodeTraverser->addVisitor($this->expressionNodeVisitor);
$nodeTraverser->addVisitor($this->fileInfoNodeVisitor);
return $nodeTraverser->traverse($nodes);
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
use Rector\NodeTypeResolver\Node\Attribute;
final class FileInfoNodeVisitor extends NodeVisitorAbstract
{
/**
* @var CurrentFileInfoProvider
*/
private $currentFileInfoProvider;
public function __construct(CurrentFileInfoProvider $currentFileInfoProvider)
{
$this->currentFileInfoProvider = $currentFileInfoProvider;
}
public function enterNode(Node $node): ?Node
{
$node->setAttribute(Attribute::FILE_INFO, $this->currentFileInfoProvider->getSmartFileInfo());
return $node;
}
}

View File

@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector\String_;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Scalar\String_;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use function Safe\sprintf;
/**
* @see https://wiki.php.net/rfc/deprecate-bareword-strings
* @see https://3v4l.org/56ZAu
*/
final class BarewordStringRector extends AbstractRector
{
/**
* @var string[]
*/
private $undefinedConstants = [];
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Changes unquoted non-existing constants to strings', [
new CodeSample('var_dump(VAR);', 'var("VAR");'),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ConstFetch::class];
}
/**
* @param ConstFetch $node
*/
public function refactor(Node $node): ?Node
{
$constantName = (string) $node->name;
if (defined($constantName)) {
return null;
}
// load the file!
/** @var SmartFileInfo $fileInfo */
$fileInfo = $node->getAttribute(Attribute::FILE_INFO);
$this->undefinedConstants = [];
$previousErrorHandler = set_error_handler(function ($severity, $message, $file, $line): void {
$match = Strings::match($message, '#Use of undefined constant (?<constant>\w+)#');
if ($match) {
$this->undefinedConstants[] = $match['constant'];
}
});
include $fileInfo->getRealPath();
// restore
set_error_handler($previousErrorHandler);
if (! in_array($constantName, $this->undefinedConstants, true)) {
return null;
}
// wrap to explicit string
return new String_(sprintf('%s', $constantName));
}
}

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\String_\BarewordStringRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Php\Rector\String_\BarewordStringRector
*/
final class BarewordStringRectorTest 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,11 @@
<?php
var_dump('SOME_STRING');
var_dump(true);
var_dump(false);
var_dump(NULL);
var_dump(__DIR__);
const THIS_EXISTS = 'yes';
var_dump(THIS_EXISTS);

View File

@ -0,0 +1,11 @@
<?php
var_dump(SOME_STRING);
var_dump(true);
var_dump(false);
var_dump(NULL);
var_dump(__DIR__);
const THIS_EXISTS = 'yes';
var_dump(THIS_EXISTS);

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\String_\BarewordStringRector: ~

View File

@ -62,6 +62,8 @@ parameters:
# false positive, has annotation type above
- '#Access to an undefined property PhpParser\\Node::\$name#' # 11
- '#Access to an undefined property PhpParser\\Node\\Expr::\$expr#'
# false positive
- '#Call to function in_array\(\) with arguments string, array\(\) and true will always evaluate to false#'
# subtype
- '#Property PhpParser\\Node\\Param::\$type \(PhpParser\\Node\\Name|PhpParser\\Node\\NullableType\|string\|null\) does not accept PhpParser\\Node\\Identifier|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType#' # 3

View File

@ -5,6 +5,7 @@ namespace Rector\Application;
use PhpParser\Lexer;
use PhpParser\Node;
use Rector\NodeTraverser\RectorNodeTraverser;
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Rector\Parser\Parser;
use Rector\Printer\FormatPerservingPrinter;
@ -37,22 +38,31 @@ final class FileProcessor
*/
private $nodeScopeAndMetadataDecorator;
/**
* @var CurrentFileInfoProvider
*/
private $currentFileInfoProvider;
public function __construct(
FormatPerservingPrinter $formatPerservingPrinter,
Parser $parser,
Lexer $lexer,
RectorNodeTraverser $rectorNodeTraverser,
NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator
NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator,
CurrentFileInfoProvider $currentFileInfoProvider
) {
$this->formatPerservingPrinter = $formatPerservingPrinter;
$this->parser = $parser;
$this->lexer = $lexer;
$this->rectorNodeTraverser = $rectorNodeTraverser;
$this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator;
$this->currentFileInfoProvider = $currentFileInfoProvider;
}
public function processFile(SmartFileInfo $smartFileInfo): string
{
$this->currentFileInfoProvider->setCurrentFileInfo($smartFileInfo);
[$newStmts, $oldStmts, $oldTokens] = $this->parseAndTraverseFileInfoToNodes($smartFileInfo);
return $this->formatPerservingPrinter->printToFile($smartFileInfo, $newStmts, $oldStmts, $oldTokens);
@ -63,6 +73,8 @@ final class FileProcessor
*/
public function processFileToString(SmartFileInfo $smartFileInfo): string
{
$this->currentFileInfoProvider->setCurrentFileInfo($smartFileInfo);
[$newStmts, $oldStmts, $oldTokens] = $this->parseAndTraverseFileInfoToNodes($smartFileInfo);
return $this->formatPerservingPrinter->printToString($newStmts, $oldStmts, $oldTokens);