diff --git a/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc new file mode 100644 index 00000000..415cd038 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/rename.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/skip_existing_name.php.inc b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/skip_existing_name.php.inc new file mode 100644 index 00000000..c001f833 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/Fixture/skip_existing_name.php.inc @@ -0,0 +1,16 @@ +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_/PreferTestsWithCamelCaseRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/config/configured_rule.php new file mode 100644 index 00000000..641703e9 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(PreferTestsWithCamelCaseRector::class); +}; diff --git a/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc new file mode 100644 index 00000000..4a25ebed --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/rename.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/skip_existing_name.php.inc b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/skip_existing_name.php.inc new file mode 100644 index 00000000..748670a0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/Fixture/skip_existing_name.php.inc @@ -0,0 +1,16 @@ +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_/PreferTestsWithSnakeCaseRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/config/configured_rule.php new file mode 100644 index 00000000..0232f132 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(PreferTestsWithSnakeCaseRector::class); +}; diff --git a/rules/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector.php b/rules/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector.php new file mode 100644 index 00000000..0a4d5f29 --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/PreferTestsWithCamelCaseRector.php @@ -0,0 +1,107 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + $hasChanged = false; + + foreach ($node->getMethods() as $classMethod) { + if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + continue; + } + + $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; + } + + if ($hasChanged === false) { + return null; + } + + return $node; + } + + private function toCamelCase(string $value): string + { + $words = explode(' ', str_replace(['-', '_'], ' ', $value)); + $words = array_map(fn (string $word): string => ucfirst($word), $words); + + return lcfirst(implode($words)); + } +} diff --git a/rules/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector.php b/rules/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector.php new file mode 100644 index 00000000..6e5a3838 --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/PreferTestsWithSnakeCaseRector.php @@ -0,0 +1,111 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + $hasChanged = false; + + foreach ($node->getMethods() as $classMethod) { + if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + continue; + } + + $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; + } + + if ($hasChanged === false) { + return null; + } + + return $node; + } + + private 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); + } +}