From 5a7cc941ac036ec3c6e2860351dd2cfbead684e3 Mon Sep 17 00:00:00 2001 From: Caleb White Date: Sun, 14 Sep 2025 14:23:34 -0500 Subject: [PATCH] fix: EnumCaseToPascalCaseRector bugs --- .../Fixture/case_on_self.php.inc | 31 +++++++++ .../Fixture/skip_enum_const.php.inc | 15 ++++ .../skip_enum_usage_in_other_package.php.inc | 2 +- .../Source/{ => Autoload}/StatusEnum.php | 2 +- .../WithAutoloadPathsTest.php | 28 -------- .../config/configured_rule.php | 3 +- .../config/with_autoload_configured_rule.php | 10 --- .../Enum_/EnumCaseToPascalCaseRector.php | 69 ++++++++----------- 8 files changed, 80 insertions(+), 80 deletions(-) create mode 100644 rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/case_on_self.php.inc create mode 100644 rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/skip_enum_const.php.inc rename rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Source/{ => Autoload}/StatusEnum.php (86%) delete mode 100644 rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/WithAutoloadPathsTest.php delete mode 100644 rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/with_autoload_configured_rule.php diff --git a/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/case_on_self.php.inc b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/case_on_self.php.inc new file mode 100644 index 00000000000..e07d8d31f14 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/case_on_self.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/skip_enum_const.php.inc b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/skip_enum_const.php.inc new file mode 100644 index 00000000000..cebfb93ff2c --- /dev/null +++ b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/Fixture/skip_enum_const.php.inc @@ -0,0 +1,15 @@ +doTestFile($filePath, true); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/with_autoload_configured_rule.php'; - } -} diff --git a/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/configured_rule.php b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/configured_rule.php index 372d2669825..d6130322c5a 100644 --- a/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/configured_rule.php +++ b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/configured_rule.php @@ -6,4 +6,5 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withRules([EnumCaseToPascalCaseRector::class]); + ->withRules([EnumCaseToPascalCaseRector::class]) + ->withAutoloadPaths([__DIR__ . '/../Source/Autoload']); diff --git a/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/with_autoload_configured_rule.php b/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/with_autoload_configured_rule.php deleted file mode 100644 index 8a4fd8fdc29..00000000000 --- a/rules-tests/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector/config/with_autoload_configured_rule.php +++ /dev/null @@ -1,10 +0,0 @@ -withRules([EnumCaseToPascalCaseRector::class]) - ->withAutoloadPaths([__DIR__ . '/../Source']); diff --git a/rules/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector.php b/rules/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector.php index 130166d9d55..19fa922ab1f 100644 --- a/rules/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector.php +++ b/rules/CodingStyle/Rector/Enum_/EnumCaseToPascalCaseRector.php @@ -4,19 +4,15 @@ namespace Rector\CodingStyle\Rector\Enum_; +use PHPStan\Reflection\ClassReflection; use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\EnumCase; -use PHPStan\BetterReflection\Reflection\ReflectionEnum; -use PHPStan\BetterReflection\Reflector\DefaultReflector; -use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound; -use PHPStan\Reflection\ReflectionProvider; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; -use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; use Rector\Rector\AbstractRector; use Rector\Skipper\FileSystem\PathNormalizer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -27,12 +23,6 @@ */ final class EnumCaseToPascalCaseRector extends AbstractRector { - public function __construct( - private readonly ReflectionProvider $reflectionProvider, - private readonly DynamicSourceLocatorProvider $dynamicSourceLocatorProvider, - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -122,67 +112,68 @@ private function refactorClassConstFetch(ClassConstFetch $classConstFetch): ?Nod return null; } - if ($this->nodeTypeResolver->getType($classConstFetch->class)->isEnum()->no()) { - return null; - } - $constName = $classConstFetch->name->toString(); + $pascalCaseName = $this->convertToPascalCase($constName); - // Skip "class" constant - if ($constName === 'class') { + // short circuit if already in pascal case + if ($constName === $pascalCaseName) { return null; } - $enumClassName = $classConstFetch->class->toString(); - if (! $this->reflectionProvider->hasClass($enumClassName)) { + $classReflection = $this->nodeTypeResolver->getType($classConstFetch->class) + ->getObjectClassReflections()[0] ?? null; + + if ($classReflection === null || ! $classReflection->isEnum()) { return null; } - $sourceLocator = $this->dynamicSourceLocatorProvider->provide(); - $defaultReflector = new DefaultReflector($sourceLocator); - - try { - $classIdentifier = $defaultReflector->reflectClass($classConstFetch->class->toString()); - } catch (IdentifierNotFound) { - // source is outside the paths defined in withPaths(), eg: vendor + if (! $this->isEnumCase($classReflection, $constName, $pascalCaseName)) { return null; } - // ensure exactly ReflectionEnum - if (! $classIdentifier instanceof ReflectionEnum) { + if ($this->isUsedOutsideOfProject($classReflection)) { return null; } - // ensure not part of definition in ->withAutoloadPaths() - $fileTarget = $classIdentifier->getFileName(); + $classConstFetch->name = new Identifier($pascalCaseName); + return $classConstFetch; + } + + private function isUsedOutsideOfProject(ClassReflection $classReflection): bool + { + $fileTarget = $classReflection->getFileName(); // possibly native if ($fileTarget === null) { - return null; + return true; } $autoloadPaths = SimpleParameterProvider::provideArrayParameter(Option::AUTOLOAD_PATHS); $normalizedFileTarget = PathNormalizer::normalize((string) realpath($fileTarget)); + if (str_contains($normalizedFileTarget, '/vendor/')) { + return true; + } + foreach ($autoloadPaths as $autoloadPath) { $normalizedAutoloadPath = PathNormalizer::normalize($autoloadPath); if ($autoloadPath === $fileTarget) { - return null; + return true; } if (str_starts_with($normalizedFileTarget, $normalizedAutoloadPath . '/')) { - return null; + return true; } } - $pascalCaseName = $this->convertToPascalCase($constName); - if ($constName !== $pascalCaseName) { - $classConstFetch->name = new Identifier($pascalCaseName); - return $classConstFetch; - } + return false; + } - return null; + private function isEnumCase(ClassReflection $classReflection, string $name, string $pascalName): bool + { + // the enum case might have already been renamed, need to check both + return $classReflection->hasEnumCase($name) || $classReflection->hasEnumCase($pascalName); } private function convertToPascalCase(string $name): string