Skip to content

Commit

Permalink
[TypeDeclaration] Add TypedPropertyFromCreateMockAssignRector (#6177)
Browse files Browse the repository at this point in the history
* [TypeDeclaration] Add TypedPropertyFromCreateMockAssignRector

* add to type declaration set

* [ci-review] Rector Rectify

---------

Co-authored-by: GitHub Action <actions@github.com>
  • Loading branch information
TomasVotruba and actions-user committed Jul 23, 2024
1 parent b0c89a9 commit 417b208
Show file tree
Hide file tree
Showing 17 changed files with 342 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Utils\Rector\Tests\Rector\RenameSimpleRectorTest
* @see \Rector\Tests\TypeDeclaration\Rector\RenameSimpleRectorTest
*/
final class RenameSimpleRector extends AbstractRector
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Utils\Rector\Tests\Rector\RenameSimpleRector;
namespace Rector\Tests\TypeDeclaration\Rector\RenameSimpleRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"symplify/rule-doc-generator": "^12.2.2",
"symplify/vendor-patches": "^11.3",
"tomasvotruba/class-leak": "^0.2.15",
"tomasvotruba/unused-public": "^0.3.9",
"tomasvotruba/unused-public": "^0.3.10",
"tracy/tracy": "^2.9"
},
"replace": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function binaryOr(?SomeInstance $someClass)
{
if ($someClass || $someClass->someMethod()) {
return 'yes';
}

return 'no';
}

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

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Fixture;

use PHPUnit\Framework\TestCase;
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeMockedClass;

class SomeTest extends TestCase
{
public $someMock;

protected function setUp(): void
{
$this->someMock = $this->createMock(SomeMockedClass::class);
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Fixture;

use PHPUnit\Framework\TestCase;
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeMockedClass;

class SomeTest extends TestCase
{
public \PHPUnit\Framework\MockObject\MockObject $someMock;

protected function setUp(): void
{
$this->someMock = $this->createMock(SomeMockedClass::class);
}
}

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

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Fixture;

use PHPUnit\Framework\TestCase;
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeFinalizedMockedClass;
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeMockedClass;

class WithFinalizedType extends TestCase
{
public $someMock;

protected function setUp(): void
{
$this->someMock = $this->createMock(SomeFinalizedMockedClass::class);
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Fixture;

use PHPUnit\Framework\TestCase;
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeFinalizedMockedClass;
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeMockedClass;

class WithFinalizedType extends TestCase
{
public \PHPUnit\Framework\MockObject\MockObject $someMock;

protected function setUp(): void
{
$this->someMock = $this->createMock(SomeFinalizedMockedClass::class);
}
}

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

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source;

final class SomeFinalizedMockedClass
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source;

class SomeMockedClass
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector;

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

final class TypedPropertyFromCreateMockAssignRectorTest 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/rule_config.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);
use Rector\ValueObject\PhpVersionFeature;

use Rector\Config\RectorConfig;
use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector;

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

declare(strict_types=1);

namespace Rector\TypeDeclaration\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\NodeManipulator\ClassMethodPropertyFetchManipulator;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\MethodName;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\TypedPropertyFromCreateMockAssignRectorTest
*/
final class TypedPropertyFromCreateMockAssignRector extends AbstractRector implements MinPhpVersionInterface
{
/**
* @var string
*/
private const TEST_CASE_CLASS = 'PHPUnit\Framework\TestCase';

/**
* @var string
*/
private const MOCK_OBJECT_CLASS = 'PHPUnit\Framework\MockObject\MockObject';

public function __construct(
private readonly ClassMethodPropertyFetchManipulator $classMethodPropertyFetchManipulator
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add typed property from assigned mock', [
new CodeSample(
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
private $someProperty;
protected function setUp(): void
{
$this->someProperty = $this->createMock(SomeMockedClass::class);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
private \PHPUnit\Framework\MockObject\MockObject $someProperty;
protected function setUp(): void
{
$this->someProperty = $this->createMock(SomeMockedClass::class);
}
}
CODE_SAMPLE
),
]);
}

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

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

$hasChanged = false;
foreach ($node->getProperties() as $property) {
// already typed
if ($property->type instanceof Node) {
continue;
}

$propertyName = $this->getName($property);

$setUpClassMethod = $node->getMethod(MethodName::SET_UP);
if (! $setUpClassMethod instanceof ClassMethod) {
continue;
}

$assignedType = $this->resolveSingleAssignedExprType($setUpClassMethod, $propertyName);
if (! $assignedType instanceof Type) {
continue;
}

if (! $this->isMockObjectType($assignedType)) {
continue;
}

$property->type = new FullyQualified(self::MOCK_OBJECT_CLASS);
$hasChanged = true;
}

if (! $hasChanged) {
return null;
}

return $node;
}

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

private function isMockObjectType(Type $type): bool
{
if ($type instanceof ObjectType && $type->isInstanceOf(self::MOCK_OBJECT_CLASS)->yes()) {
return true;
}

return $this->isIntersectionWithMockObjectType($type);
}

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

if (count($type->getTypes()) !== 2) {
return false;
}

return in_array(self::MOCK_OBJECT_CLASS, $type->getObjectClassNames());
}

private function resolveSingleAssignedExprType(ClassMethod $setUpClassMethod, string $propertyName): ?Type
{
$assignedExprs = $this->classMethodPropertyFetchManipulator->findAssignsToPropertyName(
$setUpClassMethod,
$propertyName
);
if (count($assignedExprs) !== 1) {
return null;
}

$assignedExpr = $assignedExprs[0];
$exprType = $this->getType($assignedExpr);

// work around finalized class mock
if ($exprType instanceof NeverType && $assignedExpr instanceof MethodCall && $this->isName(
$assignedExpr->name,
'createMock'
)) {
return new ObjectType(self::MOCK_OBJECT_CLASS);
}

return $exprType;
}
}
3 changes: 3 additions & 0 deletions src/Config/Level/TypeDeclarationLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Rector\TypeDeclaration\Rector\Class_\MergeDateTimePropertyTypeDeclarationRector;
use Rector\TypeDeclaration\Rector\Class_\PropertyTypeFromStrictSetterGetterRector;
use Rector\TypeDeclaration\Rector\Class_\ReturnTypeFromStrictTernaryRector;
use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector;
use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromJMSSerializerAttributeTypeRector;
use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector;
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeBasedOnPHPUnitDataProviderRector;
Expand Down Expand Up @@ -68,6 +69,8 @@ final class TypeDeclarationLevel
AddTestsVoidReturnTypeWhereNoReturnRector::class,

ReturnTypeFromMockObjectRector::class,
TypedPropertyFromCreateMockAssignRector::class,

AddArrowFunctionReturnTypeRector::class,
BoolReturnTypeFromBooleanConstReturnsRector::class,
ReturnTypeFromStrictNewArrayRector::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,11 @@ public function processNodes(
$this->processAssign($node, $mutatingScope);

if ($node->var instanceof Variable && $node->var->name instanceof Expr) {
$this->nodeScopeResolverProcessNodes([new Expression($node->var), new Expression($node->expr)], $mutatingScope, $nodeCallback);
$this->nodeScopeResolverProcessNodes(
[new Expression($node->var), new Expression($node->expr)],
$mutatingScope,
$nodeCallback
);
}

return;
Expand Down
2 changes: 1 addition & 1 deletion templates/custom-rule/utils/rector/src/Rector/__Name__.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Utils\Rector\Tests\Rector\__Name__\__Name__Test
* @see \Rector\Tests\TypeDeclaration\Rector\__Name__\__Name__Test
*/
final class __Name__ extends AbstractRector
{
Expand Down
Loading

0 comments on commit 417b208

Please sign in to comment.