From 8065f326a7825a5e1c5a4993338fbf18a6e863cc Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 29 Jan 2026 11:38:40 +0100 Subject: [PATCH] [code-quality] Add SuffixMockObjectPropertyRector, not enabled --- .../Fixture/some_test.php.inc | 49 ++++++ .../SuffixMockObjectPropertyRectorTest.php | 28 ++++ .../config/configured_rule.php | 9 ++ .../MockObjectPropertyDetector.php | 21 +++ .../Class_/SuffixMockObjectPropertyRector.php | 146 ++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/Fixture/some_test.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/SuffixMockObjectPropertyRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/config/configured_rule.php create mode 100644 rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php create mode 100644 rules/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector.php diff --git a/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/Fixture/some_test.php.inc b/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/Fixture/some_test.php.inc new file mode 100644 index 00000000..6f69b64b --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/Fixture/some_test.php.inc @@ -0,0 +1,49 @@ +someProperty = $this->createMock(\stdClass::class); + } + + public function testMocks(): void + { + $this->someProperty->expects($this->once())->method('someMethod')->willReturn('someValue'); + } +} + +?> +----- +somePropertyMock = $this->createMock(\stdClass::class); + } + + public function testMocks(): void + { + $this->somePropertyMock->expects($this->once())->method('someMethod')->willReturn('someValue'); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/SuffixMockObjectPropertyRectorTest.php b/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/SuffixMockObjectPropertyRectorTest.php new file mode 100644 index 00000000..5bdbb913 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/SuffixMockObjectPropertyRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/config/configured_rule.php new file mode 100644 index 00000000..e7a296d0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([SuffixMockObjectPropertyRector::class]); diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php new file mode 100644 index 00000000..2e97092c --- /dev/null +++ b/rules/CodeQuality/NodeAnalyser/MockObjectPropertyDetector.php @@ -0,0 +1,21 @@ +type instanceof FullyQualified) { + return false; + } + + return $property->type->toString() === PHPUnitClassName::MOCK_OBJECT; + } +} diff --git a/rules/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector.php b/rules/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector.php new file mode 100644 index 00000000..0de9aebb --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/SuffixMockObjectPropertyRector.php @@ -0,0 +1,146 @@ +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> + */ + 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; + }); + } +}