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,49 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SuffixMockObjectPropertyRector\Fixture;

use PHPUnit\Framework\TestCase;

final class SomeFileTest extends TestCase
{
private \PHPUnit\Framework\MockObject\MockObject $someProperty;

protected function setUp(): void
{
$this->someProperty = $this->createMock(\stdClass::class);
}

public function testMocks(): void
{
$this->someProperty->expects($this->once())->method('someMethod')->willReturn('someValue');
}
}

?>
-----
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SuffixMockObjectPropertyRector\Fixture;

use PHPUnit\Framework\TestCase;

final class SomeFileTest extends TestCase
{
private \PHPUnit\Framework\MockObject\MockObject $somePropertyMock;

protected function setUp(): void
{
$this->somePropertyMock = $this->createMock(\stdClass::class);
}

public function testMocks(): void
{
$this->somePropertyMock->expects($this->once())->method('someMethod')->willReturn('someValue');
}
}

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

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SuffixMockObjectPropertyRector;

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

final class SuffixMockObjectPropertyRectorTest 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,9 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\PHPUnit\CodeQuality\Rector\Class_\SuffixMockObjectPropertyRector;

return RectorConfig::configure()
->withRules([SuffixMockObjectPropertyRector::class]);
21 changes: 21 additions & 0 deletions rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\CodeQuality\NodeAnalyser;

use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Property;
use Rector\PHPUnit\Enum\PHPUnitClassName;

final class MockObjectPropertyDetector
{
public function detect(Property $property): bool
{
if (! $property->type instanceof FullyQualified) {
return false;
}

return $property->type->toString() === PHPUnitClassName::MOCK_OBJECT;
}
}
146 changes: 146 additions & 0 deletions rules/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\CodeQuality\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\VarLikeIdentifier;
use Rector\PHPUnit\CodeQuality\NodeAnalyser\MockObjectPropertyDetector;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SuffixMockObjectPropertyRector\SuffixMockObjectPropertyRectorTest
*/
final class SuffixMockObjectPropertyRector extends AbstractRector
{
public function __construct(
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
private readonly MockObjectPropertyDetector $mockObjectPropertyDetector
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Suffix mock object property names with "Mock" to clearly separate from real objects later on',
[
new CodeSample(
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;

final class MockingEntity extends TestCase
{
private MockObject $simpleObject;

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

public function test()
{
$this->simpleObject->method('someMethod')->willReturn('someValue');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;

final class MockingEntity extends TestCase
{
private MockObject $simpleObjectMock;

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

public function test()
{
$this->simpleObjectMock->method('someMethod')->willReturn('someValue');
}
}
CODE_SAMPLE
),
]
);
}

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

/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Class_
{
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
return null;
}

$hasChanged = false;

foreach ($node->getProperties() as $property) {
if (! $this->mockObjectPropertyDetector->detect($property)) {
continue;
}

$propertyName = $this->getName($property);
if (str_ends_with($propertyName, 'Mock')) {
continue;
}

$newPropertyName = $propertyName . 'Mock';
$property->props[0]->name = new VarLikeIdentifier($newPropertyName);

$this->renamePropertyUsagesInClass($node, $propertyName, $newPropertyName);
$hasChanged = true;
}

if (! $hasChanged) {
return null;
}

return $node;
}

private function renamePropertyUsagesInClass(Class_ $class, string $oldPropertyName, string $newPropertyName): void
{
$this->traverseNodesWithCallable($class, function (Node $node) use (
$oldPropertyName,
$newPropertyName
): ?Node {
if (! $node instanceof PropertyFetch) {
return null;
}

// is local property?
if (! $node->var instanceof Variable && ! $this->isName($node->var, 'this')) {
return null;
}

if (! $this->isName($node->name, $oldPropertyName)) {
return null;
}

$node->name = new Identifier($newPropertyName);
return $node;
});
}
}