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',