cover nullable type

This commit is contained in:
TomasVotruba 2020-07-19 20:19:25 +02:00
parent e9fea42472
commit 14474809d9
8 changed files with 160 additions and 49 deletions

View File

@ -4,15 +4,8 @@ declare(strict_types=1);
namespace Rector\FamilyTree\Reflection;
use PhpParser\Node\Expr;
final class FamilyRelationsAnalyzer
{
/**
* @var string[]
*/
private const KNOWN_PARENT_CLASSES = [\PhpParser\Node::class, Expr::class];
/**
* @return string[]
*/
@ -36,10 +29,6 @@ final class FamilyRelationsAnalyzer
public function isParentClass(string $class): bool
{
if (in_array($class, self::KNOWN_PARENT_CLASSES, true)) {
return true;
}
foreach (get_declared_classes() as $declaredClass) {
if ($declaredClass === $class) {
continue;

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\Utils;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
final class TypeUnwrapper
{
/**
* E.g. null|ClassType ClassType
*/
public function unwrapNullableType(UnionType $unionType): ?Type
{
if (count($unionType->getTypes()) !== 2) {
return null;
}
if (! $unionType->isSuperTypeOf(new NullType())->yes()) {
return null;
}
foreach ($unionType->getTypes() as $unionedType) {
if ($unionedType instanceof NullType) {
continue;
}
return $unionedType;
}
return null;
}
}

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Rector\Naming\Naming;
use Nette\Utils\Strings;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
@ -13,6 +12,7 @@ use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
final class PropertyNaming
{
@ -26,10 +26,20 @@ final class PropertyNaming
*/
private const INTERFACE = 'Interface';
/**
* @var TypeUnwrapper
*/
private $typeUnwrapper;
public function __construct(TypeUnwrapper $typeUnwrapper)
{
$this->typeUnwrapper = $typeUnwrapper;
}
public function getExpectedNameFromType(Type $type): ?string
{
if ($type instanceof UnionType) {
$type = $this->unwrapNullableType($type);
$type = $this->typeUnwrapper->unwrapNullableType($type);
}
if (! $type instanceof TypeWithClassName) {
@ -155,30 +165,6 @@ final class PropertyNaming
return $shortClassName;
}
/**
* E.g. null|ClassType ClassType
*/
private function unwrapNullableType(UnionType $unionType): ?Type
{
if (count($unionType->getTypes()) !== 2) {
return null;
}
if (! $unionType->isSuperTypeOf(new NullType())->yes()) {
return null;
}
foreach ($unionType->getTypes() as $unionedType) {
if ($unionedType instanceof NullType) {
continue;
}
return $unionedType;
}
return null;
}
private function isNumberOrUpper(string $char): bool
{
if (ctype_upper($char)) {

View File

@ -38,9 +38,9 @@ final class VarTagValueNodeRenamer
{
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parent instanceof Expression) {
$expressionPhpDocInfo = $parent->getAttribute(AttributeKey::PHP_DOC_INFO);
$expression = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
if ($expression instanceof Node) {
$expressionPhpDocInfo = $expression->getAttribute(AttributeKey::PHP_DOC_INFO);
}
return $expressionPhpDocInfo ?? $phpDocInfo;

View File

@ -10,7 +10,9 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
@ -22,12 +24,18 @@ use Rector\Naming\NamingConvention\NamingConventionAnalyzer;
use Rector\Naming\PhpDoc\VarTagValueNodeRenamer;
use Rector\Naming\ValueObject\VariableAndCallAssign;
use Rector\Naming\VariableRenamer;
use Rector\PHPStanStaticTypeMapper\Utils\TypeUnwrapper;
/**
* @see \Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\RenameVariableToMatchGetMethodNameRectorTest
*/
final class RenameVariableToMatchGetMethodNameRector extends AbstractRector
{
/**
* @var string[]
*/
private const ALLOWED_PARENT_TYPES = [ClassLike::class];
/**
* @var ExpectedNameResolver
*/
@ -63,6 +71,11 @@ final class RenameVariableToMatchGetMethodNameRector extends AbstractRector
*/
private $varTagValueNodeRenamer;
/**
* @var TypeUnwrapper
*/
private $typeUtils;
public function __construct(
ExpectedNameResolver $expectedNameResolver,
VariableRenamer $variableRenamer,
@ -70,7 +83,8 @@ final class RenameVariableToMatchGetMethodNameRector extends AbstractRector
FamilyRelationsAnalyzer $familyRelationsAnalyzer,
VariableAndCallAssignMatcher $variableAndCallAssignMatcher,
NamingConventionAnalyzer $namingConventionAnalyzer,
VarTagValueNodeRenamer $varTagValueNodeRenamer
VarTagValueNodeRenamer $varTagValueNodeRenamer,
TypeUnwrapper $typeUtils
) {
$this->expectedNameResolver = $expectedNameResolver;
$this->variableRenamer = $variableRenamer;
@ -79,6 +93,7 @@ final class RenameVariableToMatchGetMethodNameRector extends AbstractRector
$this->variableAndCallAssignMatcher = $variableAndCallAssignMatcher;
$this->namingConventionAnalyzer = $namingConventionAnalyzer;
$this->varTagValueNodeRenamer = $varTagValueNodeRenamer;
$this->typeUtils = $typeUtils;
}
public function getDefinition(): RectorDefinition
@ -135,25 +150,25 @@ PHP
return null;
}
return $this->renameVariable($variableAndCallAssign, $expectedName);
$this->renameVariable($variableAndCallAssign, $expectedName);
return $node;
}
private function renameVariable(VariableAndCallAssign $variableAndCallAssign, string $newName): Assign
private function renameVariable(VariableAndCallAssign $variableAndCallAssign, string $expectedName): void
{
$this->varTagValueNodeRenamer->renameAssignVarTagVariableName(
$variableAndCallAssign->getAssign(),
$variableAndCallAssign->getVariableName(),
$newName
$expectedName
);
$this->variableRenamer->renameVariableInFunctionLike(
$variableAndCallAssign->getFunctionLike(),
$variableAndCallAssign->getAssign(),
$variableAndCallAssign->getVariableName(),
$newName
$expectedName
);
return $variableAndCallAssign->getAssign();
}
/**
@ -162,10 +177,19 @@ PHP
private function isClassTypeWithChildren(Expr $expr): bool
{
$callStaticType = $this->getStaticType($expr);
if ($callStaticType instanceof UnionType) {
$callStaticType = $this->typeUtils->unwrapNullableType($callStaticType);
}
if (! $callStaticType instanceof TypeWithClassName) {
return false;
}
if (in_array($callStaticType->getClassName(), self::ALLOWED_PARENT_TYPES, true)) {
return false;
}
return $this->familyRelationsAnalyzer->isParentClass($callStaticType->getClassName());
}

View File

@ -12,6 +12,7 @@ use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\Naming\PhpDoc\VarTagValueNodeRenamer;
use Rector\NodeNameResolver\NodeNameResolver;
final class VariableRenamer
@ -26,10 +27,19 @@ final class VariableRenamer
*/
private $nodeNameResolver;
public function __construct(CallableNodeTraverser $callableNodeTraverser, NodeNameResolver $nodeNameResolver)
{
/**
* @var VarTagValueNodeRenamer
*/
private $varTagValueNodeRenamer;
public function __construct(
CallableNodeTraverser $callableNodeTraverser,
NodeNameResolver $nodeNameResolver,
VarTagValueNodeRenamer $varTagValueNodeRenamer
) {
$this->callableNodeTraverser = $callableNodeTraverser;
$this->nodeNameResolver = $nodeNameResolver;
$this->varTagValueNodeRenamer = $varTagValueNodeRenamer;
}
/**
@ -64,6 +74,7 @@ final class VariableRenamer
}
$node->name = $expectedName;
$this->varTagValueNodeRenamer->renameAssignVarTagVariableName($node, $oldName, $expectedName);
return $node;
}

View File

@ -0,0 +1,51 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use Rector\NodeTypeResolver\Node\AttributeKey;
class RenameVariableExpression
{
private function shouldSkip(ClassMethod $classMethod): bool
{
/** @var ClassLike|null $class */
$class = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($class === null) {
return true;
}
/** @var Interface_ $class */
return ! (bool) $class->extends;
}
}
?>
-----
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use Rector\NodeTypeResolver\Node\AttributeKey;
class RenameVariableExpression
{
private function shouldSkip(ClassMethod $classMethod): bool
{
/** @var ClassLike|null $classLike */
$classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE);
if ($classLike === null) {
return true;
}
/** @var Interface_ $classLike */
return ! (bool) $classLike->extends;
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\Naming\Tests\Rector\Assign\RenameVariableToMatchGetMethodNameRector\Fixture;
use PhpParser\Node\Expr\Array_;
use Rector\NodeTypeResolver\Node\AttributeKey;
class SkipParentNode
{
public function run(Array_ $array)
{
$parent = $array->getAttribute(AttributeKey::PARENT_NODE);
}
}