Skip to content

Commit

Permalink
Add RemoveTypedPropertyNonMockDocblockRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Oct 11, 2024
1 parent d553e84 commit c6f510e
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 14 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.5",
"doctrine/inflector": "^2.0.10",
"illuminate/container": "^11.22.0",
"illuminate/container": "^11.25",
"nette/utils": "^4.0",
"nikic/php-parser": "^4.19.4",
"ocramius/package-versions": "^2.9",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Fixture;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Source\RealType;

final class SomeTest extends TestCase
{
/**
* @var RealType|MockObject
*/
private RealType $someProperty;
}

?>
-----
<?php

namespace Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Fixture;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Source\RealType;

final class SomeTest extends TestCase
{
private RealType $someProperty;
}

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

namespace Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Fixture;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Source\RealType;

final class SkipMissingPropertyType extends TestCase
{
/**
* @var RealType|MockObject
*/
private $someProperty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Fixture;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Source\stdClass;
use Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Source\RealType;

final class SkipNonMockUnion extends TestCase
{
/**
* @var RealType|stdClass
*/
private RealType $someProperty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector;

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

final class RemoveTypedPropertyNonMockDocblockRectorTest 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\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector\Source;

final class RealType
{

}
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\DeadCode\Rector\ClassLike\RemoveTypedPropertyNonMockDocblockRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(RemoveTypedPropertyNonMockDocblockRector::class);
};
19 changes: 19 additions & 0 deletions rules/DeadCode/PhpDoc/TagRemover/VarTagRemover.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Rector\DeadCode\PhpDoc\TagRemover;

use PhpParser\Node;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
Expand Down Expand Up @@ -52,6 +53,24 @@ public function removeVarTagIfUseless(PhpDocInfo $phpDocInfo, Property $property
return true;
}

/**
* @api generic
*/
public function removeVarTag(Node $node): bool
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);

$varTagValueNode = $phpDocInfo->getVarTagValueNode();
if (! $varTagValueNode instanceof VarTagValueNode) {
return false;
}

$phpDocInfo->removeByType(VarTagValueNode::class);
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);

return true;
}

public function removeVarPhpTagValueNodeIfNotComment(Expression | Property | Param $node, Type $type): void
{
if ($type instanceof TemplateObjectWithoutClassType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

declare(strict_types=1);

namespace Rector\DeadCode\Rector\ClassLike;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\ObjectType;
use PHPStan\Type\UnionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover;
use Rector\Enum\ClassName;
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\ClassLike\RemoveTypedPropertyNonMockDocblockRector\RemoveTypedPropertyNonMockDocblockRectorTest
*/
final class RemoveTypedPropertyNonMockDocblockRector extends AbstractRector implements MinPhpVersionInterface
{
/**
* @var string
*/
private const MOCK_OBJECT_CLASS = 'PHPUnit\Framework\MockObject\MockObject';

public function __construct(
private readonly VarTagRemover $varTagRemover,
private readonly StaticTypeMapper $staticTypeMapper,
private readonly PhpDocInfoFactory $phpDocInfoFactory
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Remove @var annotation for PHPUnit\Framework\MockObject\MockObject combined with native object type',
[
new CodeSample(
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
final class SomeTest extends TestCase
{
/**
* @var SomeClass|MockObject
*/
private SomeClass $someProperty;
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
final class SomeTest extends TestCase
{
private SomeClass $someProperty;
}
CODE_SAMPLE
),
]
);
}

public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isObjectType($node, new ObjectType(ClassName::TEST_CASE_CLASS))) {
return null;
}

$hasChanged = false;

foreach ($node->getProperties() as $property) {
// not yet typed
if (! $property->type instanceof Node) {
continue;
}

if (count($property->props) !== 1) {
continue;
}

if ($this->isObjectType($property->type, new ObjectType(self::MOCK_OBJECT_CLASS))) {
continue;
}

$propertyDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);

if (! $this->isVarTagUnionTypeMockObject($propertyDocInfo, $property)) {
continue;
}

// clear var docblock
if ($this->varTagRemover->removeVarTag($property)) {
$hasChanged = true;
}
}

if (! $hasChanged) {
return null;
}

return $node;
}

public function provideMinPhpVersion(): int
{
return PhpVersionFeature::TYPED_PROPERTIES;
}

private function isVarTagUnionTypeMockObject(PhpDocInfo $phpDocInfo, Property $property): bool
{
$varTagValueNode = $phpDocInfo->getVarTagValueNode();
if (! $varTagValueNode instanceof VarTagValueNode) {
return false;
}

if (! $varTagValueNode->type instanceof UnionTypeNode) {
return false;
}

$varTagType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property);
if (! $varTagType instanceof UnionType) {
return false;
}

foreach ($varTagType->getTypes() as $unionedType) {
if ($unionedType->isSuperTypeOf(new ObjectType(self::MOCK_OBJECT_CLASS))->yes()) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Type\IntersectionType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\Enum\ClassName;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Rector\AbstractScopeAwareRector;
use Rector\TypeDeclaration\NodeAnalyzer\ReturnAnalyzer;
Expand All @@ -27,11 +28,6 @@
*/
final class ReturnTypeFromMockObjectRector extends AbstractScopeAwareRector implements MinPhpVersionInterface
{
/**
* @var string
*/
private const TESTCASE_CLASS = 'PHPUnit\Framework\TestCase';

/**
* @var string
*/
Expand Down Expand Up @@ -155,6 +151,6 @@ private function isInsideTestCaseClass(Scope $scope): bool
}

// is phpunit test case?
return $classReflection->isSubclassOf(self::TESTCASE_CLASS);
return $classReflection->isSubclassOf(ClassName::TEST_CASE_CLASS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\Enum\ClassName;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\Rector\AbstractRector;
use Rector\StaticTypeMapper\StaticTypeMapper;
Expand All @@ -24,11 +25,6 @@
*/
final class TypedPropertyFromCreateMockAssignRector extends AbstractRector implements MinPhpVersionInterface
{
/**
* @var string
*/
private const TEST_CASE_CLASS = 'PHPUnit\Framework\TestCase';

/**
* @var string
*/
Expand All @@ -37,7 +33,7 @@ final class TypedPropertyFromCreateMockAssignRector extends AbstractRector imple
public function __construct(
private readonly AssignToPropertyTypeInferer $assignToPropertyTypeInferer,
private readonly StaticTypeMapper $staticTypeMapper,
private readonly ConstructorAssignDetector $constructorAssignDetector
private readonly ConstructorAssignDetector $constructorAssignDetector,
) {
}

Expand Down Expand Up @@ -86,7 +82,7 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
if (! $this->isObjectType($node, new ObjectType(self::TEST_CASE_CLASS))) {
if (! $this->isObjectType($node, new ObjectType(ClassName::TEST_CASE_CLASS))) {
return null;
}

Expand Down
13 changes: 13 additions & 0 deletions src/Enum/ClassName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Rector\Enum;

final class ClassName
{
/**
* @var string
*/
public const TEST_CASE_CLASS = 'PHPUnit\Framework\TestCase';
}

0 comments on commit c6f510e

Please sign in to comment.