Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/set/type-declaration-docblocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnDocblockForScalarArrayFromAssignsRector;
use Rector\TypeDeclarationDocblocks\Rector\Class_\AddReturnDocblockDataProviderRector;
use Rector\TypeDeclarationDocblocks\Rector\Class_\ClassMethodArrayDocblockParamFromLocalCallsRector;
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector;
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector;
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector;
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDimFetchAccessRector;
Expand All @@ -22,6 +23,7 @@
$rectorConfig->rules([
// property var
DocblockVarFromParamDocblockInConstructorRector::class,
DocblockVarArrayFromPropertyDefaultsRector::class,

// param
AddParamArrayDocblockFromDimFetchAccessRector::class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class DocblockVarArrayFromPropertyDefaultsRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::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,8 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;

final class SkipMultipleProperties
{
private array $names, $surnames = ['Jim', 'Rohn'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;

final class SomeClass
{
private array $names;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;

final class SkipNonArrayPropertyType
{
private $names = ['Jim', 'Rohn'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;

final class SomeClass
{
private array $names = ['Jim', 'Rohn'];
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector\Fixture;

final class SomeClass
{
/**
* @var string[]
*/
private array $names = ['Jim', 'Rohn'];
}

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

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(DocblockVarArrayFromPropertyDefaultsRector::class);
};
133 changes: 133 additions & 0 deletions rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclarationDocblocks;

use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Privatization\TypeManipulator\TypeNormalizer;
use Rector\StaticTypeMapper\StaticTypeMapper;

final readonly class NodeDocblockTypeDecorator
{
public function __construct(
private TypeNormalizer $typeNormalizer,
private StaticTypeMapper $staticTypeMapper,
private DocBlockUpdater $docBlockUpdater,
) {
}

public function decorateGenericIterableParamType(
Type $type,
PhpDocInfo $phpDocInfo,
ClassMethod $classMethod,
string $parameterName
): bool {
if ($this->isBareMixedType($type)) {
// no value
return false;
}

$normalizedType = $this->typeNormalizer->generalizeConstantTypes($type);
$typeNode = $this->createTypeNode($normalizedType);

$paramTagValueNode = new ParamTagValueNode($typeNode, false, '$' . $parameterName, '', false);

$this->addTagValueNodeAndUpdatePhpDocInfo($phpDocInfo, $paramTagValueNode, $classMethod);

return true;
}

public function decorateGenericIterableReturnType(
Type $type,
PhpDocInfo $classMethodPhpDocInfo,
ClassMethod $classMethod
): bool {
$typeNode = $this->createTypeNode($type);

if ($this->isBareMixedType($type)) {
// no value
return false;
}

$returnTagValueNode = new ReturnTagValueNode($typeNode, '');

$this->addTagValueNodeAndUpdatePhpDocInfo($classMethodPhpDocInfo, $returnTagValueNode, $classMethod);

return true;
}

public function decorateGenericIterableVarType(Type $type, PhpDocInfo $phpDocInfo, Property $property): bool
{
$typeNode = $this->createTypeNode($type);

if ($this->isBareMixedType($type)) {
// no value
return false;
}

$varTagValueNode = new VarTagValueNode($typeNode, '', '');

$this->addTagValueNodeAndUpdatePhpDocInfo($phpDocInfo, $varTagValueNode, $property);

return true;
}

private function createTypeNode(Type $type): TypeNode
{
$generalizedReturnType = $this->typeNormalizer->generalizeConstantTypes($type);

// turn into rather generic short return type, to keep it open to extension later and readable to human
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($generalizedReturnType);

if ($typeNode instanceof IdentifierTypeNode && $typeNode->name === 'mixed') {
return new ArrayTypeNode($typeNode);
}

return $typeNode;
}

private function addTagValueNodeAndUpdatePhpDocInfo(
PhpDocInfo $phpDocInfo,
ParamTagValueNode|VarTagValueNode|ReturnTagValueNode $tagValueNode,
Property|ClassMethod $stmt
): void {
$phpDocInfo->addTagValueNode($tagValueNode);

$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($stmt);
}

private function isBareMixedType(Type $type): bool
{
$normalizedResolvedParameterType = $this->typeNormalizer->generalizeConstantTypes($type);

// most likely mixed, skip
return $this->isArrayMixed($normalizedResolvedParameterType);
}

private function isArrayMixed(Type $type): bool
{
if (! $type instanceof ArrayType) {
return false;
}

if (! $type->getItemType() instanceof MixedType) {
return false;
}

return $type->getKeyType() instanceof IntegerType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Privatization\TypeManipulator\TypeNormalizer;
use Rector\Rector\AbstractRector;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedGenericObjectType;
use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator;
use Rector\TypeDeclarationDocblocks\NodeFinder\DataProviderMethodsFinder;
use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder;
use Rector\TypeDeclarationDocblocks\NodeFinder\YieldNodeFinder;
Expand All @@ -36,11 +31,9 @@ public function __construct(
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
private readonly DataProviderMethodsFinder $dataProviderMethodsFinder,
private readonly ReturnNodeFinder $returnNodeFinder,
private readonly StaticTypeMapper $staticTypeMapper,
private readonly DocBlockUpdater $docBlockUpdater,
private readonly TypeNormalizer $typeNormalizer,
private readonly YieldTypeResolver $yieldTypeResolver,
private readonly YieldNodeFinder $yieldNodeFinder,
private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator,
) {
}

Expand Down Expand Up @@ -140,12 +133,16 @@ public function refactor(Node $node): ?Node

$soleReturnType = $this->getType($soleReturn->expr);

$this->addGeneratedTypeReturnDocblockType(
$hasClassMethodChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableReturnType(
$soleReturnType,
$classMethodPhpDocInfo,
$dataProviderClassMethod
);

if (! $hasClassMethodChanged) {
continue;
}

$hasChanged = true;
continue;
}
Expand All @@ -159,7 +156,16 @@ public function refactor(Node $node): ?Node
$yieldType = new FullyQualifiedGenericObjectType('Iterator', $yieldType->getTypes());
}

$this->addGeneratedTypeReturnDocblockType($yieldType, $classMethodPhpDocInfo, $dataProviderClassMethod);
$hasClassMethodChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableReturnType(
$yieldType,
$classMethodPhpDocInfo,
$dataProviderClassMethod
);

if (! $hasClassMethodChanged) {
continue;
}

$hasChanged = true;
}
}
Expand All @@ -170,20 +176,4 @@ public function refactor(Node $node): ?Node

return $node;
}

private function addGeneratedTypeReturnDocblockType(
Type $soleReturnType,
PhpDocInfo $classMethodPhpDocInfo,
ClassMethod $dataProviderClassMethod
): void {
$generalizedReturnType = $this->typeNormalizer->generalizeConstantTypes($soleReturnType);

// turn into rather generic short return type, to keep it open to extension later and readable to human
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($generalizedReturnType);

$returnTagValueNode = new ReturnTagValueNode($typeNode, '');
$classMethodPhpDocInfo->addTagValueNode($returnTagValueNode);

$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($dataProviderClassMethod);
}
}
Loading
Loading