diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md index e460902499e..683d94acf92 100644 --- a/build/target-repository/docs/rector_rules_overview.md +++ b/build/target-repository/docs/rector_rules_overview.md @@ -5510,6 +5510,8 @@ Add override attribute to overridden methods Add type to constants +:wrench: **configure it!** + - class: [`Rector\Php83\Rector\ClassConst\AddTypeToConstRector`](../rules/Php83/Rector/ClassConst/AddTypeToConstRector.php) ```diff @@ -5522,6 +5524,22 @@ Add type to constants
+```diff + class ParentClass extends ChildClass + { +- public const TYPE = 'some_type'; ++ public const string TYPE = 'some_type'; + public const OTHER_TYPE = 'other_type'; + } + + class ChildClass + { + public const OTHER_TYPE = 'other_type'; + } +``` + +
+ ### CombineHostPortLdapUriRector Combine separated host and port on `ldap_connect()` args diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/AllowHasChildRectorTest.php b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/AllowHasChildRectorTest.php new file mode 100644 index 00000000000..7aa091aaa15 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/AllowHasChildRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixtureAllowHasChild'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/allow_has_child.php'; + } +} diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/FixtureAllowHasChild/apply_to_public_const_when_no_child_classes.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/FixtureAllowHasChild/apply_to_public_const_when_no_child_classes.php.inc new file mode 100644 index 00000000000..49ed5c43cde --- /dev/null +++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/FixtureAllowHasChild/apply_to_public_const_when_no_child_classes.php.inc @@ -0,0 +1,21 @@ + +----- + diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/FixtureAllowHasChild/apply_type_to_class_const_when_not_overridden.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/FixtureAllowHasChild/apply_type_to_class_const_when_not_overridden.php.inc new file mode 100644 index 00000000000..1bcf6fd6905 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/FixtureAllowHasChild/apply_type_to_class_const_when_not_overridden.php.inc @@ -0,0 +1,39 @@ + +----- + diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/config/allow_has_child.php b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/config/allow_has_child.php new file mode 100644 index 00000000000..58d22e2c53d --- /dev/null +++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/config/allow_has_child.php @@ -0,0 +1,15 @@ +ruleWithConfiguration(AddTypeToConstRector::class, [ + AddTypeToConstRector::ALLOW_HAS_CHILD => true, + ]); + + $rectorConfig->phpVersion(PhpVersion::PHP_83); +}; diff --git a/rules/Php74/Rector/FuncCall/HebrevcToNl2brHebrevRector.php b/rules/Php74/Rector/FuncCall/HebrevcToNl2brHebrevRector.php index 65ffdcb93b8..0f4cd2bbf02 100644 --- a/rules/Php74/Rector/FuncCall/HebrevcToNl2brHebrevRector.php +++ b/rules/Php74/Rector/FuncCall/HebrevcToNl2brHebrevRector.php @@ -65,11 +65,6 @@ public function refactor(Node $node): ?FuncCall } $node->name = new Name('hebrev'); - return new FuncCall( - new Name('nl2br'), - [ - new Arg($node) - ] - ); + return new FuncCall(new Name('nl2br'), [new Arg($node)]); } } diff --git a/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php b/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php index b2474721540..d893913a206 100644 --- a/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php +++ b/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php @@ -20,29 +20,43 @@ use PhpParser\Node\Stmt\ClassConst; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; +use Rector\Contract\Rector\ConfigurableRectorInterface; +use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; +use Rector\PhpParser\AstResolver; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; use Rector\Rector\AbstractRector; use Rector\StaticTypeMapper\StaticTypeMapper; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Webmozart\Assert\Assert; /** * @see \Rector\Tests\Php83\Rector\ClassConst\AddTypeToConstRector\AddTypeToConstRectorTest */ -final class AddTypeToConstRector extends AbstractRector implements MinPhpVersionInterface +final class AddTypeToConstRector extends AbstractRector implements ConfigurableRectorInterface, MinPhpVersionInterface { + /** + * @api + * @var string + */ + final public const ALLOW_HAS_CHILD = 'allow_has_child'; + + private bool $allowHasChild = false; + public function __construct( private readonly ReflectionProvider $reflectionProvider, - private readonly StaticTypeMapper $staticTypeMapper + private readonly StaticTypeMapper $staticTypeMapper, + private readonly FamilyRelationsAnalyzer $familyRelationsAnalyzer, + private readonly AstResolver $astResolver, ) { } public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Add type to constants', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' final class SomeClass { @@ -57,6 +71,40 @@ final class SomeClass } CODE_SAMPLE , + [ + AddTypeToConstRector::ALLOW_HAS_CHILD => false, + ] + ), + new ConfiguredCodeSample( + <<<'CODE_SAMPLE' +class ParentClass extends ChildClass +{ + public const TYPE = 'some_type'; + public const OTHER_TYPE = 'other_type'; +} + +class ChildClass +{ + public const OTHER_TYPE = 'other_type'; +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class ParentClass extends ChildClass +{ + public const string TYPE = 'some_type'; + public const OTHER_TYPE = 'other_type'; +} + +class ChildClass +{ + public const OTHER_TYPE = 'other_type'; +} +CODE_SAMPLE + , + [ + AddTypeToConstRector::ALLOW_HAS_CHILD => true, + ] ), ]); } @@ -86,6 +134,7 @@ public function refactor(Node $node): ?Class_ } $parentClassReflections = $this->getParentReflections($className); + $childClassReflections = $this->getChildClassReflections($className); $hasChanged = false; @@ -103,7 +152,11 @@ public function refactor(Node $node): ?Class_ continue; } - if ($this->canBeInherited($classConst, $node)) { + if (! $this->allowHasChild && $this->canBeInherited($classConst, $node)) { + continue; + } + + if ($this->allowHasChild && $this->isConstGuardedByChildren($constNode, $childClassReflections)) { continue; } @@ -139,6 +192,16 @@ public function refactor(Node $node): ?Class_ return $node; } + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + $ignoreInheritance = $configuration[self::ALLOW_HAS_CHILD] ?? (bool) current($configuration); + Assert::boolean($ignoreInheritance); + $this->allowHasChild = $ignoreInheritance; + } + public function provideMinPhpVersion(): int { return PhpVersionFeature::TYPED_CLASS_CONSTANTS; @@ -160,6 +223,29 @@ public function isConstGuardedByParents(Const_ $const, array $parentClassReflect return false; } + /** + * @param ClassReflection[] $childClassReflections + */ + private function isConstGuardedByChildren(Const_ $const, array $childClassReflections): bool + { + foreach ($childClassReflections as $childClassReflection) { + $classLike = $this->astResolver->resolveClassFromClassReflection($childClassReflection); + if (! $classLike instanceof Class_) { + continue; + } + + foreach ($classLike->getConstants() as $childClassConstants) { + foreach ($childClassConstants->consts as $childConst) { + if ($this->nodeComparator->areNodesEqual($childConst, $const)) { + return true; + } + } + } + } + + return false; + } + private function findValueType(Expr $expr): ?Identifier { if ($expr instanceof UnaryPlus || $expr instanceof UnaryMinus) { @@ -220,6 +306,20 @@ private function getParentReflections(string $className): array $currentClassReflection !== $classReflection); } + /** + * @return ClassReflection[] + */ + private function getChildClassReflections(string $className): array + { + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $currentClassReflection = $this->reflectionProvider->getClass($className); + + return $this->familyRelationsAnalyzer->getChildrenOfClassReflection($currentClassReflection); + } + private function canBeInherited(ClassConst $classConst, Class_ $class): bool { return ! $class->isFinal() && ! $classConst->isPrivate() && ! $classConst->isFinal(); diff --git a/src/StaticTypeMapper/PhpParser/NameNodeMapper.php b/src/StaticTypeMapper/PhpParser/NameNodeMapper.php index 1953b9f4de9..e81c3e908cf 100644 --- a/src/StaticTypeMapper/PhpParser/NameNodeMapper.php +++ b/src/StaticTypeMapper/PhpParser/NameNodeMapper.php @@ -61,7 +61,7 @@ private function expandedNamespacedName(Node $node): ?FullyQualified return null; } - if (!$node->hasAttribute(AttributeKey::NAMESPACED_NAME)) { + if (! $node->hasAttribute(AttributeKey::NAMESPACED_NAME)) { return null; }