[StrictCodeQuality] Add freshly created node support to var inl… (#2122)

[StrictCodeQuality] Add freshly created node support to var inline assert
This commit is contained in:
Tomáš Votruba 2019-10-08 19:08:55 +01:00 committed by GitHub
commit cf63ed7817
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 12 deletions

View File

@ -3,15 +3,22 @@
namespace Rector\StrictCodeQuality\Rector\Stmt;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
@ -69,46 +76,145 @@ PHP
return null;
}
$variable = $this->betterNodeFinder->findFirstInstanceOf($node, Variable::class);
// skip properties
if ($node instanceof Property) {
return null;
}
$docVariableName = $this->getVarDocVariableName($phpDocInfo);
if ($docVariableName === null) {
return null;
}
$variable = $this->findVariableByName($node, $docVariableName);
if (! $variable instanceof Variable) {
return null;
}
$variable->setAttribute('comments', []);
$type = $phpDocInfo->getVarType();
$isVariableJustCreated = $this->isVariableJustCreated($node, $docVariableName);
if ($isVariableJustCreated === false) {
return $this->refactorFreshlyCreatedNode($node, $phpDocInfo, $variable);
}
$assertFuncCall = null;
return $this->refactorAlreadyCreatedNode($node, $phpDocInfo, $variable);
}
private function createFuncCallBasedOnType(Type $type, Variable $variable): ?FuncCall
{
if ($type instanceof ObjectType) {
$instanceOf = new Instanceof_($variable, new FullyQualified($type->getClassName()));
$assertFuncCall = $this->createFunction('assert', [$instanceOf]);
return $this->createFunction('assert', [$instanceOf]);
}
if ($type instanceof IntegerType) {
$isInt = $this->createFunction('is_int', [$variable]);
$assertFuncCall = $this->createFunction('assert', [$isInt]);
return $this->createFunction('assert', [$isInt]);
}
if ($type instanceof FloatType) {
$isFloat = $this->createFunction('is_float', [$variable]);
return $this->createFunction('assert', [$isFloat]);
}
if ($type instanceof StringType) {
$isInt = $this->createFunction('is_string', [$variable]);
$assertFuncCall = $this->createFunction('assert', [$isInt]);
$isString = $this->createFunction('is_string', [$variable]);
return $this->createFunction('assert', [$isString]);
}
if ($type instanceof BooleanType) {
$isInt = $this->createFunction('is_bool', [$variable]);
$assertFuncCall = $this->createFunction('assert', [$isInt]);
return $this->createFunction('assert', [$isInt]);
}
if ($assertFuncCall === null) {
return null;
}
private function isVariableJustCreated(Node $node, string $docVariableName): bool
{
if (! $node instanceof Expression) {
return false;
}
if (! $node->expr instanceof Assign) {
return false;
}
$assign = $node->expr;
// the variable is on the left side = just created
if (! $assign->var instanceof Variable) {
return false;
}
return $this->isName($assign->var, $docVariableName);
}
private function getVarDocVariableName(PhpDocInfo $phpDocInfo): ?string
{
$varTagValueNode = $phpDocInfo->getVarTagValue();
if ($varTagValueNode === null) {
return null;
}
$this->addNodeBeforeNode($assertFuncCall, $node);
$variableName = $varTagValueNode->variableName;
// no variable
if (empty($variableName)) {
return null;
}
// cleanup @var annotation
return ltrim($variableName, '$');
}
private function removeVarAnnotation(Node $node, PhpDocInfo $phpDocInfo): void
{
$varTagValueNode = $phpDocInfo->getByType(VarTagValueNode::class);
$phpDocInfo->removeTagValueNodeFromNode($varTagValueNode);
$this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo);
}
private function findVariableByName(Stmt $stmt, string $docVariableName): ?Variable
{
return $this->betterNodeFinder->findFirst($stmt, function (Node $stmt) use ($docVariableName): bool {
if (! $stmt instanceof Variable) {
return false;
}
return $this->isName($stmt, $docVariableName);
});
}
private function refactorFreshlyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, Variable $variable): ?Node
{
$node->setAttribute('comments', []);
$type = $phpDocInfo->getVarType();
$assertFuncCall = $this->createFuncCallBasedOnType($type, $variable);
if ($assertFuncCall === null) {
return null;
}
$this->removeVarAnnotation($variable, $phpDocInfo);
$this->addNodeBeforeNode($assertFuncCall, $node);
return $node;
}
private function refactorAlreadyCreatedNode(Node $node, PhpDocInfo $phpDocInfo, Variable $variable): ?Node
{
$varTagValue = $phpDocInfo->getVarTagValue();
$phpStanType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType(
$varTagValue->type,
$variable
);
$assertFuncCall = $this->createFuncCallBasedOnType($phpStanType, $variable);
if ($assertFuncCall === null) {
return null;
}
$this->removeVarAnnotation($variable, $phpDocInfo);
$this->addNodeAfterNode($assertFuncCall, $node);
return $node;
}

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\StrictCodeQuality\Tests\Rector\Stmt\VarInlineAnnotationToAssertRector\Fixture;
class AssignFreshVar
{
public function run($value)
{
/** @var int $int */
$int = $value;
}
}
?>
-----
<?php
namespace Rector\StrictCodeQuality\Tests\Rector\Stmt\VarInlineAnnotationToAssertRector\Fixture;
class AssignFreshVar
{
public function run($value)
{
/** @var int $int */
$int = $value;
assert(is_int($int));
}
}
?>

View File

@ -0,0 +1,12 @@
<?php
namespace Rector\StrictCodeQuality\Tests\Rector\Stmt\VarInlineAnnotationToAssertRector\Fixture;
class SkipMissingVariable
{
public function run($value)
{
/** @var int */
$int = $value;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Rector\StrictCodeQuality\Tests\Rector\Stmt\VarInlineAnnotationToAssertRector\Fixture;
class SkipProperty
{
/**
* @var int $id
*/
public $id;
}

View File

@ -20,6 +20,9 @@ final class VarInlineAnnotationToAssertRectorTest extends AbstractRectorTestCase
{
yield [__DIR__ . '/Fixture/fixture.php.inc'];
yield [__DIR__ . '/Fixture/scalar.php.inc'];
yield [__DIR__ . '/Fixture/assign_fresh_var.php.inc'];
yield [__DIR__ . '/Fixture/skip_missing_variable.php.inc'];
yield [__DIR__ . '/Fixture/skip_property.php.inc'];
}
protected function getRectorClass(): string

View File

@ -203,3 +203,4 @@ parameters:
# known value
- '#Parameter \#1 \$node of method Rector\\DeadCode\\Rector\\ClassMethod\\RemoveDelegatingParentCallRector\:\:unwrapExpression\(\) expects PhpParser\\Node, PhpParser\\Node\\Stmt\|null given#'
- '#Method Rector\\StrictCodeQuality\\Rector\\Stmt\\VarInlineAnnotationToAssertRector\:\:findVariableByName\(\) should return PhpParser\\Node\\Expr\\Variable\|null but returns PhpParser\\Node\|null#'