From f8ebda92a8be264e2575d58b8bd83547d5da2c22 Mon Sep 17 00:00:00 2001 From: Max Hoogenbosch Date: Fri, 3 Apr 2026 12:34:32 +0200 Subject: [PATCH 1/3] Add rule to convert phpunit methods to snake case --- .../Fixture/rename.php.inc | 37 +++++++ .../PreferTestsWithSnakeCaseRectorTest.php | 28 ++++++ .../config/configured_rule.php | 10 ++ .../PreferTestsWithSnakeCaseRector.php | 97 +++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/PreferTestsWithSnakeCaseRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/config/configured_rule.php create mode 100644 rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc new file mode 100644 index 00000000..88f2c48c --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/PreferTestsWithSnakeCaseRectorTest.php b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/PreferTestsWithSnakeCaseRectorTest.php new file mode 100644 index 00000000..686af464 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/PreferTestsWithSnakeCaseRectorTest.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/ClassMethod/PreferTestsWithSnakeCaseRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/config/configured_rule.php new file mode 100644 index 00000000..e7faaf99 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(PreferTestsWithSnakeCaseRector::class); +}; diff --git a/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php b/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php new file mode 100644 index 00000000..2be20c1a --- /dev/null +++ b/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php @@ -0,0 +1,97 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) { + return null; + } + + $currentName = $node->name->toString(); + $newName = $this->toSnakeCase($currentName); + + if ($currentName === $newName) { + return null; + } + + $node->name = new Node\Identifier($newName); + + return $node; + } + + public function toSnakeCase(string $value): string + { + if (ctype_lower($value)) { + return $value; + } + + $value = (string) preg_replace('/\s+/u', '', ucwords($value)); + $value = (string) preg_replace('/(.)(?=[A-Z])/u', '$1_', $value); + + return strtolower($value); + } +} From cc3684d0ef6b9e62cf98a0230374bc2886e18bb8 Mon Sep 17 00:00:00 2001 From: Max Hoogenbosch Date: Fri, 3 Apr 2026 12:48:10 +0200 Subject: [PATCH 2/3] Add rule to convert phpunit methods to camel case --- .../Fixture/rename.php.inc | 37 ++++++++ .../PreferTestsWithCamelCaseRectorTest.php | 28 ++++++ .../config/configured_rule.php | 10 ++ .../PreferTestsWithCamelCaseRector.php | 93 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/PreferTestsWithCamelCaseRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/config/configured_rule.php create mode 100644 rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc new file mode 100644 index 00000000..2151c12c --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/PreferTestsWithCamelCaseRectorTest.php b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/PreferTestsWithCamelCaseRectorTest.php new file mode 100644 index 00000000..507409a0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/PreferTestsWithCamelCaseRectorTest.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/ClassMethod/PreferTestsWithCamelCaseRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/config/configured_rule.php new file mode 100644 index 00000000..ac1d7c14 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(PreferTestsWithCamelCaseRector::class); +}; diff --git a/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php b/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php new file mode 100644 index 00000000..a70e5062 --- /dev/null +++ b/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php @@ -0,0 +1,93 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) { + return null; + } + + $currentName = $node->name->toString(); + $newName = $this->toCamelCase($currentName); + + if ($currentName === $newName) { + return null; + } + + $node->name = new Node\Identifier($newName); + + return $node; + } + + public function toCamelCase(string $value): string + { + $words = explode(' ', str_replace(['-', '_'], ' ', $value)); + $words = array_map(fn (string $word) => ucfirst($word), $words); + + return lcfirst(implode($words)); + } +} From 2fdf38bfbc15a1f24cb9aca5c2cd2678d0c5e933 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 26 Jun 2026 11:32:51 +0200 Subject: [PATCH 3/3] address review: use Class_ node type, skip rename on existing method name collision --- .../Fixture/rename.php.inc | 4 +- .../Fixture/skip_existing_name.php.inc | 16 +++++++ .../PreferTestsWithCamelCaseRectorTest.php | 2 +- .../config/configured_rule.php | 2 +- .../Fixture/rename.php.inc | 4 +- .../Fixture/skip_existing_name.php.inc | 16 +++++++ .../PreferTestsWithSnakeCaseRectorTest.php | 2 +- .../config/configured_rule.php | 2 +- .../PreferTestsWithCamelCaseRector.php | 46 ++++++++++++------- .../PreferTestsWithSnakeCaseRector.php | 44 ++++++++++++------ 10 files changed, 99 insertions(+), 39 deletions(-) rename rules-tests/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc (65%) create mode 100644 rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/skip_existing_name.php.inc rename rules-tests/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithCamelCaseRector/PreferTestsWithCamelCaseRectorTest.php (86%) rename rules-tests/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithCamelCaseRector/config/configured_rule.php (70%) rename rules-tests/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc (65%) create mode 100644 rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/skip_existing_name.php.inc rename rules-tests/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithSnakeCaseRector/PreferTestsWithSnakeCaseRectorTest.php (86%) rename rules-tests/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithSnakeCaseRector/config/configured_rule.php (70%) rename rules/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithCamelCaseRector.php (54%) rename rules/CodeQuality/Rector/{ClassMethod => Class_}/PreferTestsWithSnakeCaseRector.php (58%) diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc similarity index 65% rename from rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc rename to rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc index 2151c12c..415cd038 100644 --- a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc @@ -1,6 +1,6 @@ rule(PreferTestsWithCamelCaseRector::class); diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc similarity index 65% rename from rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc rename to rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc index 88f2c48c..4a25ebed 100644 --- a/rules-tests/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc @@ -1,6 +1,6 @@ rule(PreferTestsWithSnakeCaseRector::class); diff --git a/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php b/rules/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector.php similarity index 54% rename from rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php rename to rules/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector.php index a70e5062..0a4d5f29 100644 --- a/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithCamelCaseRector.php +++ b/rules/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace Rector\PHPUnit\CodeQuality\Rector\ClassMethod; +namespace Rector\PHPUnit\CodeQuality\Rector\Class_; use PhpParser\Node; -use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Class_; 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\ClassMethod\PreferTestsWithCamelCaseRector\PreferTestsWithCamelCaseRectorTest + * @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\PreferTestsWithCamelCaseRector\PreferTestsWithCamelCaseRectorTest */ final class PreferTestsWithCamelCaseRector extends AbstractRector { @@ -23,7 +23,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Changes PHPUnit tests methods to camel case', [ + return new RuleDefinition('Changes PHPUnit test methods to camel case', [ new CodeSample( <<<'CODE_SAMPLE' use PHPUnit\Framework\TestCase; @@ -55,11 +55,11 @@ public function testSomething() */ public function getNodeTypes(): array { - return [ClassMethod::class]; + return [Class_::class]; } /** - * @param ClassMethod $node + * @param Class_ $node */ public function refactor(Node $node): ?Node { @@ -67,26 +67,40 @@ public function refactor(Node $node): ?Node return null; } - if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) { - return null; - } + $hasChanged = false; - $currentName = $node->name->toString(); - $newName = $this->toCamelCase($currentName); + foreach ($node->getMethods() as $classMethod) { + if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + continue; + } - if ($currentName === $newName) { - return null; + $currentName = $classMethod->name->toString(); + $newName = $this->toCamelCase($currentName); + + if ($currentName === $newName) { + continue; + } + + // avoid name collision with an existing method + if ($node->getMethod($newName) instanceof Node) { + continue; + } + + $classMethod->name = new Node\Identifier($newName); + $hasChanged = true; } - $node->name = new Node\Identifier($newName); + if ($hasChanged === false) { + return null; + } return $node; } - public function toCamelCase(string $value): string + private function toCamelCase(string $value): string { $words = explode(' ', str_replace(['-', '_'], ' ', $value)); - $words = array_map(fn (string $word) => ucfirst($word), $words); + $words = array_map(fn (string $word): string => ucfirst($word), $words); return lcfirst(implode($words)); } diff --git a/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php b/rules/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector.php similarity index 58% rename from rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php rename to rules/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector.php index 2be20c1a..6e5a3838 100644 --- a/rules/CodeQuality/Rector/ClassMethod/PreferTestsWithSnakeCaseRector.php +++ b/rules/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace Rector\PHPUnit\CodeQuality\Rector\ClassMethod; +namespace Rector\PHPUnit\CodeQuality\Rector\Class_; use PhpParser\Node; -use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Class_; 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\ClassMethod\PreferTestsWithSnakeCaseRector\PreferTestsWithSnakeCaseRectorTest + * @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\PreferTestsWithSnakeCaseRector\PreferTestsWithSnakeCaseRectorTest */ final class PreferTestsWithSnakeCaseRector extends AbstractRector { @@ -23,7 +23,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Changes PHPUnit tests methods to snake case', [ + return new RuleDefinition('Changes PHPUnit test methods to snake case', [ new CodeSample( <<<'CODE_SAMPLE' use PHPUnit\Framework\TestCase; @@ -55,11 +55,11 @@ public function test_something() */ public function getNodeTypes(): array { - return [ClassMethod::class]; + return [Class_::class]; } /** - * @param ClassMethod $node + * @param Class_ $node */ public function refactor(Node $node): ?Node { @@ -67,23 +67,37 @@ public function refactor(Node $node): ?Node return null; } - if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) { - return null; - } + $hasChanged = false; - $currentName = $node->name->toString(); - $newName = $this->toSnakeCase($currentName); + foreach ($node->getMethods() as $classMethod) { + if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + continue; + } - if ($currentName === $newName) { - return null; + $currentName = $classMethod->name->toString(); + $newName = $this->toSnakeCase($currentName); + + if ($currentName === $newName) { + continue; + } + + // avoid name collision with an existing method + if ($node->getMethod($newName) instanceof Node) { + continue; + } + + $classMethod->name = new Node\Identifier($newName); + $hasChanged = true; } - $node->name = new Node\Identifier($newName); + if ($hasChanged === false) { + return null; + } return $node; } - public function toSnakeCase(string $value): string + private function toSnakeCase(string $value): string { if (ctype_lower($value)) { return $value;