Skip to content

Commit

Permalink
[CodeQuality] Add NarrowUnionTypeDocRector (#6262)
Browse files Browse the repository at this point in the history
Co-authored-by: kaizen-ci <[email protected]>
  • Loading branch information
samsonasik and kaizen-ci committed Apr 30, 2021
1 parent d43e40a commit e615756
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
namespace Rector\PHPStanStaticTypeMapper\TypeAnalyzer;

use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IterableType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\PHPStanStaticTypeMapper\ValueObject\UnionTypeAnalysis;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Traversable;

final class UnionTypeAnalyzer
Expand Down Expand Up @@ -58,4 +64,54 @@ public function hasTypeClassNameOnly(UnionType $unionType): bool

return true;
}

public function hasObjectWithoutClassType(UnionType $unionType): bool
{
$types = $unionType->getTypes();
foreach ($types as $type) {
if ($type instanceof ObjectWithoutClassType) {
return true;
}
}

return false;
}

public function hasObjectWithoutClassTypeWithOnlyFullyQualifiedObjectType(UnionType $unionType): bool
{
$types = $unionType->getTypes();
foreach ($types as $type) {
if ($type instanceof ObjectWithoutClassType) {
continue;
}

if (! $type instanceof FullyQualifiedObjectType) {
return false;
}
}

return true;
}

public function isScalar(UnionType $unionType): bool
{
$types = $unionType->getTypes();
foreach ($types as $type) {
if ($type instanceof StringType) {
continue;
}
if ($type instanceof FloatType) {
continue;
}
if ($type instanceof IntegerType) {
continue;
}
if ($type instanceof BooleanType) {
continue;
}
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector\Fixture;

class Fixture {
/**
* @param object|DateTime $message
*/
public function getMessage(object $message)
{
}
}

?>
-----
<?php

namespace Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector\Fixture;

class Fixture {
/**
* @param DateTime $message
*/
public function getMessage(object $message)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector\Fixture;

class ParamScalar {
/**
* @param int|string|bool|float $value
*/
public function getMessage($value)
{
}
}

?>
-----
<?php

namespace Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector\Fixture;

class ParamScalar {
/**
* @param scalar $value
*/
public function getMessage($value)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector\Fixture;

class SkipNonUnion {
/**
* @param DateTime $message
*/
public function getMessage(object $message)
{
}
}

?>

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class NarrowUnionTypeDocRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

use Rector\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(NarrowUnionTypeDocRector::class);
};
131 changes: 131 additions & 0 deletions rules/CodeQuality/Rector/ClassMethod/NarrowUnionTypeDocRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Rector\CodeQuality\Rector\ClassMethod;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\Type\UnionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Rector\AbstractRector;
use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeAnalyzer;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Tests\CodeQuality\Rector\ClassMethod\NarrowUnionTypeDocRector\NarrowUnionTypeDocRectorTest
*/
final class NarrowUnionTypeDocRector extends AbstractRector
{
/**
* @var UnionTypeAnalyzer
*/
private $unionTypeAnalyzer;

public function __construct(UnionTypeAnalyzer $unionTypeAnalyzer)
{
$this->unionTypeAnalyzer = $unionTypeAnalyzer;
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Changes docblock by narrowing type', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass {
/**
* @param object|DateTime $message
*/
public function getMessage(object $message)
{
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass {
/**
* @param DateTime $message
*/
public function getMessage(object $message)
{
}
}
CODE_SAMPLE
),
]);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
$params = $node->getParams();

foreach ($params as $key => $param) {
/** @var string $paramName */
$paramName = $this->getName($param->var);
$paramType = $phpDocInfo->getParamType($paramName);

if (! $paramType instanceof UnionType) {
continue;
}

if ($this->unionTypeAnalyzer->isScalar($paramType)) {
$this->changeDocObjectScalar($key, $phpDocInfo);
continue;
}

if ($this->unionTypeAnalyzer->hasObjectWithoutClassType($paramType)) {
$this->changeDocObjectWithoutClassType($paramType, $key, $phpDocInfo);
continue;
}
}

if ($phpDocInfo->hasChanged()) {
return $node;
}

return null;
}

private function changeDocObjectWithoutClassType(
UnionType $unionType,
int $key,
PhpDocInfo $phpDocInfo
): void {
if (! $this->unionTypeAnalyzer->hasObjectWithoutClassTypeWithOnlyFullyQualifiedObjectType($unionType)) {
return;
}

$types = $unionType->getTypes();
$resultType = '';
foreach ($types as $type) {
if ($type instanceof FullyQualifiedObjectType) {
$resultType .= $type->getClassName() . '|';
}
}

$resultType = rtrim($resultType, '|');
$paramTagValueNodes = $phpDocInfo->getParamTagValueNodes();
$paramTagValueNodes[$key]->type = new IdentifierTypeNode($resultType);
}

private function changeDocObjectScalar(int $key, PhpDocInfo $phpDocInfo): void {
$paramTagValueNodes = $phpDocInfo->getParamTagValueNodes();
$paramTagValueNodes[$key]->type = new IdentifierTypeNode('scalar');
}
}
Loading

0 comments on commit e615756

Please sign in to comment.