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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class SkipArrayNative
{
/**
* @return int[]|string[]
*/
public function run(): array
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class SkipDescription
{
/**
* @return bool|mixed|string the escaped value
*/
public function run(): false|string
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class SkipMoreSpecificDocblock
{
/**
* @return \DateTime|\DateTimeImmutable
*/
public function run(): object
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class SkipNoNativeType
{
/**
* @return bool|mixed|string
*/
public function run()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class UnionOverSingleNative
{
/**
* @return bool|float|mixed|string
*/
private function escapeQueryValue(string $value): string
{
}
}

?>
-----
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class UnionOverSingleNative
{
private function escapeQueryValue(string $value): string
{
}
}

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

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class UnionWithMixed
{
/**
* @return bool|mixed|string
*/
protected function setContactToSync(&$checkEmailsInSF, $lead): false|string
{
}
}

?>
-----
<?php

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\Fixture;

final class UnionWithMixed
{
protected function setContactToSync(&$checkEmailsInSF, $lead): false|string
{
}
}

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

declare(strict_types=1);

namespace Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;

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

final class RemoveUselessUnionReturnDocblockRectorTest 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,11 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
use Rector\ValueObject\PhpVersionFeature;

return RectorConfig::configure()
->withPhpVersion(PhpVersionFeature::UNION_TYPES)
->withRules([RemoveUselessUnionReturnDocblockRector::class]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

namespace Rector\DeadCode\Rector\ClassMethod;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Rector\AbstractRector;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector\RemoveUselessUnionReturnDocblockRectorTest
*/
final class RemoveUselessUnionReturnDocblockRector extends AbstractRector implements MinPhpVersionInterface
{
public function __construct(
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly DocBlockUpdater $docBlockUpdater,
private readonly StaticTypeMapper $staticTypeMapper,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Remove @return union docblock that is broader than a more specific, non-array native return type',
[
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
/**
* @return bool|mixed|string
*/
public function run(): false|string
{
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
public function run(): false|string
{
}
}
CODE_SAMPLE
),
]
);
}

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

/**
* @param ClassMethod|Function_ $node
*/
public function refactor(Node $node): ?Node
{
if ($node->returnType === null) {
return null;
}

// skip as no comments
if ($node->getComments() === []) {
return null;
}

$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if (! $phpDocInfo instanceof PhpDocInfo) {
return null;
}

$returnTagValueNode = $phpDocInfo->getReturnTagValue();
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
return null;
}

// keep description, it carries info the native type cannot
if ($returnTagValueNode->description !== '') {
return null;
}

// only union docblock types are handled here
if (! $returnTagValueNode->type instanceof UnionTypeNode) {
return null;
}

$nativeReturnType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($node->returnType);

// keep array native type, the docblock may carry element types
if ($nativeReturnType->isArray()->yes()) {
return null;
}

$docblockReturnType = $phpDocInfo->getReturnType();

// the docblock adds nothing when it is broader than the native type
if (! $docblockReturnType->isSuperTypeOf($nativeReturnType)->yes()) {
return null;
}

if (! $phpDocInfo->removeByType(ReturnTagValueNode::class)) {
return null;
}

$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);

return $node;
}

public function provideMinPhpVersion(): int
{
return PhpVersionFeature::UNION_TYPES;
}
}
2 changes: 2 additions & 0 deletions src/Config/Level/DeadCodeLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessParamTagRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnExprInConstructRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveVoidDocblockFromMagicMethodRector;
use Rector\DeadCode\Rector\Closure\RemoveUnusedClosureVariableUseRector;
use Rector\DeadCode\Rector\Concat\RemoveConcatAutocastRector;
Expand Down Expand Up @@ -119,6 +120,7 @@ final class DeadCodeLevel
RemoveUselessReturnTagRector::class,
RemoveDuplicatedReturnSelfDocblockRector::class,
RemoveMixedDocblockOverruledByNativeTypeRector::class,
RemoveUselessUnionReturnDocblockRector::class,
RemoveUselessReadOnlyTagRector::class,
RemoveNonExistingVarAnnotationRector::class,
RemoveUselessVarTagRector::class,
Expand Down
Loading