diff --git a/config/set/php83.php b/config/set/php83.php index e13001da14b..8e6c7ef0040 100644 --- a/config/set/php83.php +++ b/config/set/php83.php @@ -4,7 +4,6 @@ declare (strict_types=1); namespace RectorPrefix202312; use Rector\Config\RectorConfig; -use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector; return static function (RectorConfig $rectorConfig) : void { - $rectorConfig->rules([AddOverrideAttributeToOverriddenMethodsRector::class]); + $rectorConfig->rules([\Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector::class, \Rector\Php83\Rector\ClassConst\AddTypeToConstRector::class]); }; diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index 30f82a2df57..7301dfb8cbe 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 351 Rules Overview +# 353 Rules Overview
@@ -44,7 +44,7 @@ - [Php82](#php82) (4) -- [Php83](#php83) (1) +- [Php83](#php83) (2) - [Privatization](#privatization) (4) @@ -5263,6 +5263,22 @@ Add override attribute to overridden methods
+### AddTypeToConstRector + +Add const to type + +- class: [`Rector\Php83\Rector\ClassConst\AddTypeToConstRector`](../rules/Php83/Rector/ClassConst/AddTypeToConstRector.php) + +```diff + final class SomeClass + { +- public const TYPE = 'some_type'; ++ public const string TYPE = 'some_type'; + } +``` + +
+ ## Privatization ### FinalizeClassesWithoutChildrenRector diff --git a/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php b/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php new file mode 100644 index 00000000000..d6cad3b8367 --- /dev/null +++ b/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php @@ -0,0 +1,200 @@ +reflectionProvider = $reflectionProvider; + } + public function getRuleDefinition() : RuleDefinition + { + return new RuleDefinition('Add const to type', [new CodeSample(<<<'CODE_SAMPLE' +final class SomeClass +{ + public const TYPE = 'some_type'; +} +CODE_SAMPLE +, <<<'CODE_SAMPLE' +final class SomeClass +{ + public const string TYPE = 'some_type'; +} +CODE_SAMPLE +)]); + } + public function getNodeTypes() : array + { + return [Class_::class]; + } + /** + * @param Class_ $node + */ + public function refactor(Node $node) : ?\PhpParser\Node\Stmt\Class_ + { + if ($node->isAbstract()) { + return null; + } + $consts = \array_filter($node->stmts, function (Node $stmt) { + return $stmt instanceof Node\Stmt\ClassConst; + }); + if ($consts === []) { + return null; + } + try { + $parents = $this->getParents($node); + $implementations = $this->getImplementations($node); + $traits = $this->getTraits($node); + } catch (FullyQualifiedNameNotAutoloadedException $exception) { + return null; + } + $changes = \false; + foreach ($consts as $const) { + // If a type is set, skip + if ($const->type !== null) { + continue; + } + foreach ($const->consts as $constNode) { + if ($this->shouldSkipDueToInheritance($constNode, $parents, $implementations, $traits)) { + continue; + } + if ($this->canBeInheritied($const, $node)) { + continue; + } + $valueType = $this->findValueType($constNode->value); + } + if (($valueType ?? null) === null) { + continue; + } + $const->type = $valueType; + $changes = \true; + } + if (!$changes) { + return null; + } + return $node; + } + public function provideMinPhpVersion() : int + { + return PhpVersionFeature::TYPED_CLASS_CONSTANTS; + } + /** + * @param ClassReflection[] $parents + * @param ClassReflection[] $implementations + * @param ClassReflection[] $traits + */ + public function shouldSkipDueToInheritance(Node\Const_ $constNode, array $parents, array $implementations, array $traits) : bool + { + foreach ([$parents, $implementations, $traits] as $inheritance) { + foreach ($inheritance as $inheritanceItem) { + if ($constNode->name->name === '') { + continue; + } + try { + $inheritanceItem->getConstant($constNode->name->name); + return \true; + } catch (MissingConstantFromReflectionException $exception) { + } + } + } + return \false; + } + private function findValueType(Node\Expr $value) : ?Node\Identifier + { + if ($value instanceof Node\Scalar\String_) { + return new Node\Identifier('string'); + } + if ($value instanceof Node\Scalar\LNumber) { + return new Node\Identifier('int'); + } + if ($value instanceof Node\Scalar\DNumber) { + return new Node\Identifier('float'); + } + if ($value instanceof Node\Expr\ConstFetch && $value->name->toLowerString() !== 'null') { + return new Node\Identifier('bool'); + } + if ($value instanceof Node\Expr\ConstFetch && $value->name->toLowerString() === 'null') { + return new Node\Identifier('null'); + } + if ($value instanceof Node\Expr\Array_) { + return new Node\Identifier('array'); + } + return null; + } + /** + * @return ClassReflection[] + */ + private function getParents(Class_ $class) : array + { + $parents = \array_filter([$class->extends]); + return \array_map(function (Node\Name $name) : ClassReflection { + if (!$name instanceof FullyQualified) { + throw new FullyQualifiedNameNotAutoloadedException($name); + } + if ($this->reflectionProvider->hasClass($name->toString())) { + return $this->reflectionProvider->getClass($name->toString()); + } + throw new FullyQualifiedNameNotAutoloadedException($name); + }, $parents); + } + /** + * @return ClassReflection[] + */ + private function getImplementations(Class_ $class) : array + { + return \array_map(function (Node\Name $name) : ClassReflection { + if (!$name instanceof FullyQualified) { + throw new FullyQualifiedNameNotAutoloadedException($name); + } + if ($this->reflectionProvider->hasClass($name->toString())) { + return $this->reflectionProvider->getClass($name->toString()); + } + throw new FullyQualifiedNameNotAutoloadedException($name); + }, $class->implements); + } + /** + * @return ClassReflection[] + */ + private function getTraits(Class_ $node) : array + { + $traits = []; + foreach ($node->getTraitUses() as $traitUse) { + $traits = \array_merge($traits, $traitUse->traits); + } + return \array_map(function (Node\Name $name) : ClassReflection { + if (!$name instanceof FullyQualified) { + throw new FullyQualifiedNameNotAutoloadedException($name); + } + if ($this->reflectionProvider->hasClass($name->toString())) { + return $this->reflectionProvider->getClass($name->toString()); + } + throw new FullyQualifiedNameNotAutoloadedException($name); + }, $traits); + } + private function canBeInheritied(Node\Stmt\ClassConst $constNode, Class_ $node) : bool + { + return !$node->isFinal() && !$constNode->isPrivate(); + } +} diff --git a/src/Application/VersionResolver.php b/src/Application/VersionResolver.php index 23a6de395b2..60dfefda934 100644 --- a/src/Application/VersionResolver.php +++ b/src/Application/VersionResolver.php @@ -19,12 +19,12 @@ final class VersionResolver * @api * @var string */ - public const PACKAGE_VERSION = 'ea5b331e7ea67a03b4c3f162c566a8dbfd74d55d'; + public const PACKAGE_VERSION = '52b665436c0f2161619b265caa3f2a65be5604dc'; /** * @api * @var string */ - public const RELEASE_DATE = '2023-12-03 13:22:40'; + public const RELEASE_DATE = '2023-12-03 15:57:42'; /** * @var int */ diff --git a/src/Exception/FullyQualifiedNameNotAutoloadedException.php b/src/Exception/FullyQualifiedNameNotAutoloadedException.php new file mode 100644 index 00000000000..4b73ad807ec --- /dev/null +++ b/src/Exception/FullyQualifiedNameNotAutoloadedException.php @@ -0,0 +1,19 @@ +name = $name; + parent::__construct(\sprintf('%s was not autoloaded', $name->toString())); + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index 7a15d8c0201..054169a91c7 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -527,4 +527,9 @@ final class PhpVersionFeature * @var int */ public const OVERRIDE_ATTRIBUTE = \Rector\Core\ValueObject\PhpVersion::PHP_83; + /** + * @see https://wiki.php.net/rfc/typed_class_constants + * @var int + */ + public const TYPED_CLASS_CONSTANTS = \Rector\Core\ValueObject\PhpVersion::PHP_83; } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index ad3de27f863..89e2b237c58 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -1204,6 +1204,7 @@ return array( 'Rector\\Core\\Error\\ExceptionCorrector' => $baseDir . '/src/Error/ExceptionCorrector.php', 'Rector\\Core\\Exception\\Cache\\CachingException' => $baseDir . '/src/Exception/Cache/CachingException.php', 'Rector\\Core\\Exception\\Configuration\\InvalidConfigurationException' => $baseDir . '/src/Exception/Configuration/InvalidConfigurationException.php', + 'Rector\\Core\\Exception\\FullyQualifiedNameNotAutoloadedException' => $baseDir . '/src/Exception/FullyQualifiedNameNotAutoloadedException.php', 'Rector\\Core\\Exception\\NotImplementedYetException' => $baseDir . '/src/Exception/NotImplementedYetException.php', 'Rector\\Core\\Exception\\Reflection\\MissingPrivatePropertyException' => $baseDir . '/src/Exception/Reflection/MissingPrivatePropertyException.php', 'Rector\\Core\\Exception\\ShouldNotHappenException' => $baseDir . '/src/Exception/ShouldNotHappenException.php', @@ -1914,6 +1915,7 @@ return array( 'Rector\\Php82\\Rector\\FuncCall\\Utf8DecodeEncodeToMbConvertEncodingRector' => $baseDir . '/rules/Php82/Rector/FuncCall/Utf8DecodeEncodeToMbConvertEncodingRector.php', 'Rector\\Php82\\Rector\\New_\\FilesystemIteratorSkipDotsRector' => $baseDir . '/rules/Php82/Rector/New_/FilesystemIteratorSkipDotsRector.php', 'Rector\\Php82\\Rector\\Param\\AddSensitiveParameterAttributeRector' => $baseDir . '/rules/Php82/Rector/Param/AddSensitiveParameterAttributeRector.php', + 'Rector\\Php83\\Rector\\ClassConst\\AddTypeToConstRector' => $baseDir . '/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php', 'Rector\\Php83\\Rector\\ClassMethod\\AddOverrideAttributeToOverriddenMethodsRector' => $baseDir . '/rules/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector.php', 'Rector\\PhpAttribute\\AnnotationToAttributeMapper' => $baseDir . '/packages/PhpAttribute/AnnotationToAttributeMapper.php', 'Rector\\PhpAttribute\\AnnotationToAttributeMapper\\ArrayAnnotationToAttributeMapper' => $baseDir . '/packages/PhpAttribute/AnnotationToAttributeMapper/ArrayAnnotationToAttributeMapper.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 565b6f868c3..5a4935d202d 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -1422,6 +1422,7 @@ class ComposerStaticInita55c41c7fa52abd86138c6f32df1d185 'Rector\\Core\\Error\\ExceptionCorrector' => __DIR__ . '/../..' . '/src/Error/ExceptionCorrector.php', 'Rector\\Core\\Exception\\Cache\\CachingException' => __DIR__ . '/../..' . '/src/Exception/Cache/CachingException.php', 'Rector\\Core\\Exception\\Configuration\\InvalidConfigurationException' => __DIR__ . '/../..' . '/src/Exception/Configuration/InvalidConfigurationException.php', + 'Rector\\Core\\Exception\\FullyQualifiedNameNotAutoloadedException' => __DIR__ . '/../..' . '/src/Exception/FullyQualifiedNameNotAutoloadedException.php', 'Rector\\Core\\Exception\\NotImplementedYetException' => __DIR__ . '/../..' . '/src/Exception/NotImplementedYetException.php', 'Rector\\Core\\Exception\\Reflection\\MissingPrivatePropertyException' => __DIR__ . '/../..' . '/src/Exception/Reflection/MissingPrivatePropertyException.php', 'Rector\\Core\\Exception\\ShouldNotHappenException' => __DIR__ . '/../..' . '/src/Exception/ShouldNotHappenException.php', @@ -2132,6 +2133,7 @@ class ComposerStaticInita55c41c7fa52abd86138c6f32df1d185 'Rector\\Php82\\Rector\\FuncCall\\Utf8DecodeEncodeToMbConvertEncodingRector' => __DIR__ . '/../..' . '/rules/Php82/Rector/FuncCall/Utf8DecodeEncodeToMbConvertEncodingRector.php', 'Rector\\Php82\\Rector\\New_\\FilesystemIteratorSkipDotsRector' => __DIR__ . '/../..' . '/rules/Php82/Rector/New_/FilesystemIteratorSkipDotsRector.php', 'Rector\\Php82\\Rector\\Param\\AddSensitiveParameterAttributeRector' => __DIR__ . '/../..' . '/rules/Php82/Rector/Param/AddSensitiveParameterAttributeRector.php', + 'Rector\\Php83\\Rector\\ClassConst\\AddTypeToConstRector' => __DIR__ . '/../..' . '/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php', 'Rector\\Php83\\Rector\\ClassMethod\\AddOverrideAttributeToOverriddenMethodsRector' => __DIR__ . '/../..' . '/rules/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector.php', 'Rector\\PhpAttribute\\AnnotationToAttributeMapper' => __DIR__ . '/../..' . '/packages/PhpAttribute/AnnotationToAttributeMapper.php', 'Rector\\PhpAttribute\\AnnotationToAttributeMapper\\ArrayAnnotationToAttributeMapper' => __DIR__ . '/../..' . '/packages/PhpAttribute/AnnotationToAttributeMapper/ArrayAnnotationToAttributeMapper.php',