diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md index a36a1c17ca8..48194ea860f 100644 --- a/build/target-repository/docs/rector_rules_overview.md +++ b/build/target-repository/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 412 Rules Overview +# 413 Rules Overview
@@ -48,7 +48,7 @@ - [Php81](#php81) (11) -- [Php82](#php82) (1) +- [Php82](#php82) (2) - [Privatization](#privatization) (8) @@ -596,10 +596,25 @@ Change multiple null compares to ?? queue ### ConvertStaticPrivateConstantToSelfRector -Replaces static::* access to private constants with self::* on final classes +Replaces static::* access to private constants with self::* + +:wrench: **configure it!** - class: [`Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector`](../rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php) +```php +use Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector; +use Rector\Config\RectorConfig; + +return static function (RectorConfig $rectorConfig): void { + $rectorConfig->ruleWithConfiguration(ConvertStaticPrivateConstantToSelfRector::class, [ + ConvertStaticPrivateConstantToSelfRector::ENABLE_FOR_NON_FINAL_CLASSES => false, + ]); +}; +``` + +↓ + ```diff final class Foo { private const BAR = 'bar'; @@ -6511,6 +6526,21 @@ Decorate read-only class with `readonly` attribute
+### Utf8DecodeEncodeToMbConvertEncodingRector + +Change deprecated utf8_decode and utf8_encode to mb_convert_encoding + +- class: [`Rector\Php82\Rector\FuncCall\Utf8DecodeEncodeToMbConvertEncodingRector`](../rules/Php82/Rector/FuncCall/Utf8DecodeEncodeToMbConvertEncodingRector.php) + +```diff +-utf8_decode($value); +-utf8_encode($value); ++mb_convert_encoding($value, 'ISO-8859-1'); ++mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); +``` + +
+ ## Privatization ### ChangeGlobalVariablesToPropertiesRector diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/ConvertStaticPrivateConstantToSelfRectorForNonFinalClassesTest.php b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/ConvertStaticPrivateConstantToSelfRectorForNonFinalClassesTest.php new file mode 100644 index 00000000000..d73858d4ea0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/ConvertStaticPrivateConstantToSelfRectorForNonFinalClassesTest.php @@ -0,0 +1,32 @@ +doTestFile($filePath); + } + + /** + * @return Iterator> + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureEnableForNonFinalClasses'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/config_non_final_classes.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/Fixture/replace-in-private-methods.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/Fixture/replace-in-private-methods.php.inc new file mode 100644 index 00000000000..f7b2636e8ce --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/Fixture/replace-in-private-methods.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-protected.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-protected.php.inc new file mode 100644 index 00000000000..a254d511f01 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-protected.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-public.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-public.php.inc new file mode 100644 index 00000000000..948655afbe8 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/do-not-replace-if-used-in-child-classes-as-public.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/replace-if-not-used-in-child-classes.php.inc b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/replace-if-not-used-in-child-classes.php.inc new file mode 100644 index 00000000000..68075fb21f3 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/FixtureEnableForNonFinalClasses/replace-if-not-used-in-child-classes.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/config/config_non_final_classes.php b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/config/config_non_final_classes.php new file mode 100644 index 00000000000..62d719faef2 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector/config/config_non_final_classes.php @@ -0,0 +1,16 @@ +ruleWithConfiguration( + ConvertStaticPrivateConstantToSelfRector::class, + [ + ConvertStaticPrivateConstantToSelfRector::ENABLE_FOR_NON_FINAL_CLASSES => true, + ] + ); +}; diff --git a/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php b/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php index e5ec5179cf6..ed646b104d6 100644 --- a/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php +++ b/rules/CodeQuality/Rector/ClassConstFetch/ConvertStaticPrivateConstantToSelfRector.php @@ -9,8 +9,12 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ClassReflection; +use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Rector\Core\Reflection\ReflectionResolver; +use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** @@ -18,14 +22,28 @@ * @see https://3v4l.org/8Y0ba * @see https://phpstan.org/r/11d4c850-1a40-4fae-b665-291f96104d11 */ -final class ConvertStaticPrivateConstantToSelfRector extends AbstractRector +final class ConvertStaticPrivateConstantToSelfRector extends AbstractRector implements AllowEmptyConfigurableRectorInterface { + /** + * @api + * @var string + */ + public const ENABLE_FOR_NON_FINAL_CLASSES = 'enable_for_non_final_classes'; + + private bool $enableForNonFinalClasses = false; + + public function __construct( + private readonly FamilyRelationsAnalyzer $familyRelationsAnalyzer, + private readonly ReflectionResolver $reflectionResolver, + ) { + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Replaces static::* access to private constants with self::* on final classes', + 'Replaces static::* access to private constants with self::*', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' final class Foo { private const BAR = 'bar'; @@ -46,6 +64,9 @@ public function run() } CODE_SAMPLE , + [ + self::ENABLE_FOR_NON_FINAL_CLASSES => false, + ], ), ], ); @@ -56,16 +77,24 @@ public function getNodeTypes(): array return [ClassConstFetch::class]; } + public function configure(array $configuration): void + { + $this->enableForNonFinalClasses = $configuration[self::ENABLE_FOR_NON_FINAL_CLASSES] ?? (bool) current( + $configuration + ); + } + /** * @param ClassConstFetch $node */ public function refactor(Node $node): ?ClassConstFetch { - if (! $this->isUsingStatic($node)) { + $class = $this->betterNodeFinder->findParentType($node, Class_::class); + if (! $class instanceof Class_) { return null; } - if (! $this->isPrivateConstant($node)) { + if ($this->shouldBeSkipped($class, $node)) { return null; } @@ -83,30 +112,82 @@ private function isUsingStatic(ClassConstFetch $classConstFetch): bool return $classConstFetch->class->toString() === 'static'; } - private function isPrivateConstant(ClassConstFetch $classConstFetch): bool + private function isPrivateConstant(ClassConstFetch $constant, Class_ $class): bool { - $class = $this->betterNodeFinder->findParentType($classConstFetch, Class_::class); - if (! $class instanceof Class_) { + $constantName = $this->getConstantName($constant); + if ($constantName === null) { return false; } + foreach ($class->getConstants() as $classConst) { + if (! $this->nodeNameResolver->isName($classConst, $constantName)) { + continue; + } + + return $classConst->isPrivate(); + } + + return false; + } + + private function isUsedInPrivateMethod(ClassConstFetch $node): bool + { + $method = $this->betterNodeFinder->findParentType($node, Node\Stmt\ClassMethod::class); - if (! $class->isFinal()) { + if (! $method instanceof Node\Stmt\ClassMethod) { return false; } - $constantName = $classConstFetch->name; - if (! $constantName instanceof Identifier) { + return $method->flags === Class_::MODIFIER_PRIVATE; + } + + private function shouldBeSkipped(Class_ $class, ClassConstFetch $classConstFetch): bool + { + if (! $this->isUsingStatic($classConstFetch)) { + return true; + } + if (! $this->isPrivateConstant($classConstFetch, $class)) { + return true; + } + if ($this->isUsedInPrivateMethod($classConstFetch)) { return false; } - foreach ($class->getConstants() as $classConst) { - if (! $this->nodeNameResolver->isName($classConst, $constantName->toString())) { - continue; - } + if ($this->enableForNonFinalClasses) { + return $this->isOverwrittenInChildClass($classConstFetch); + } - return $classConst->isPrivate(); + return ! $class->isFinal(); + } + + private function isOverwrittenInChildClass(ClassConstFetch $classConstFetch): bool + { + $constantName = $this->getConstantName($classConstFetch); + if ($constantName === null) { + return false; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($classConstFetch); + if (! $classReflection instanceof ClassReflection) { + return false; + } + $childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection); + + foreach ($childrenClassReflections as $childrenClassReflection) { + if ($childrenClassReflection->hasConstant($constantName)) { + return true; + } } return false; } + + private function getConstantName(ClassConstFetch $classConstFetch): ?string + { + $constantNameIdentifier = $classConstFetch->name; + if (! $constantNameIdentifier instanceof Identifier) { + return null; + } + + return $constantNameIdentifier->toString(); + } }