diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md index 3f6fa376392..7b136074996 100644 --- a/build/target-repository/docs/rector_rules_overview.md +++ b/build/target-repository/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 355 Rules Overview +# 356 Rules Overview
@@ -44,6 +44,8 @@ - [Php82](#php82) (4) +- [Php83](#php83) (1) + - [Privatization](#privatization) (4) - [Removing](#removing) (5) @@ -5314,6 +5316,33 @@ Change deprecated utf8_decode and utf8_encode to mb_convert_encoding
+## Php83 + +### AddOverrideAttributeToOverriddenMethodsRector + +Add override attribute to overridden methods + +- class: [`Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector`](../rules/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector.php) + +```diff + class ParentClass + { + public function foo() + { + } + } + + class ChildClass extends ParentClass + { ++ #[\Override] + public function foo() + { + } + } +``` + +
+ ## Privatization ### FinalizeClassesWithoutChildrenRector diff --git a/config/set/level/up-to-php83.php b/config/set/level/up-to-php83.php new file mode 100644 index 00000000000..d30c096fbdb --- /dev/null +++ b/config/set/level/up-to-php83.php @@ -0,0 +1,15 @@ +sets([SetList::PHP_83, LevelSetList::UP_TO_PHP_82]); + + // parameter must be defined after import, to override imported param version + $rectorConfig->phpVersion(PhpVersion::PHP_83); +}; diff --git a/config/set/php83.php b/config/set/php83.php new file mode 100644 index 00000000000..474fd7d2d4f --- /dev/null +++ b/config/set/php83.php @@ -0,0 +1,10 @@ +rules([AddOverrideAttributeToOverriddenMethodsRector::class]); +}; diff --git a/packages/Set/ValueObject/LevelSetList.php b/packages/Set/ValueObject/LevelSetList.php index 865eb6f1b47..e0611fecdd2 100644 --- a/packages/Set/ValueObject/LevelSetList.php +++ b/packages/Set/ValueObject/LevelSetList.php @@ -11,6 +11,11 @@ */ final class LevelSetList implements SetListInterface { + /** + * @var string + */ + public const UP_TO_PHP_83 = __DIR__ . '/../../../config/set/level/up-to-php83.php'; + /** * @var string */ diff --git a/packages/Set/ValueObject/SetList.php b/packages/Set/ValueObject/SetList.php index 554fa3cefb4..c18643a87a1 100644 --- a/packages/Set/ValueObject/SetList.php +++ b/packages/Set/ValueObject/SetList.php @@ -106,6 +106,11 @@ final class SetList implements SetListInterface */ public const PHP_82 = __DIR__ . '/../../../config/set/php82.php'; + /** + * @var string + */ + public const PHP_83 = __DIR__ . '/../../../config/set/php83.php'; + /** * @var string */ diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/AddOverrideAttributeToOverriddenMethodsRectorTest.php b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/AddOverrideAttributeToOverriddenMethodsRectorTest.php new file mode 100644 index 00000000000..cb1bb9e537c --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/AddOverrideAttributeToOverriddenMethodsRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/apply_attribute_to_overridden_methods.php.inc b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/apply_attribute_to_overridden_methods.php.inc new file mode 100644 index 00000000000..40386f76494 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/apply_attribute_to_overridden_methods.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/apply_attribute_to_overridden_methods_from_grandparent.php.inc b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/apply_attribute_to_overridden_methods_from_grandparent.php.inc new file mode 100644 index 00000000000..8a4506ef1d6 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/apply_attribute_to_overridden_methods_from_grandparent.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_method_that_is_not_overridden.php.inc b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_method_that_is_not_overridden.php.inc new file mode 100644 index 00000000000..0600d2f861f --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_method_that_is_not_overridden.php.inc @@ -0,0 +1,14 @@ + diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_on_construct.php.inc b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_on_construct.php.inc new file mode 100644 index 00000000000..82a16832531 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_on_construct.php.inc @@ -0,0 +1,14 @@ + diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_on_non_inheriting_class.php.inc b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_on_non_inheriting_class.php.inc new file mode 100644 index 00000000000..6d0c8a31e42 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_on_non_inheriting_class.php.inc @@ -0,0 +1,11 @@ + diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_override_attribute_exists.php.inc b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_override_attribute_exists.php.inc new file mode 100644 index 00000000000..1bdabbd5aed --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Fixture/skip_override_attribute_exists.php.inc @@ -0,0 +1,15 @@ + diff --git a/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Source/ExampleChildClass.php b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Source/ExampleChildClass.php new file mode 100644 index 00000000000..b827ec2bb02 --- /dev/null +++ b/rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Source/ExampleChildClass.php @@ -0,0 +1,7 @@ +rule(AddOverrideAttributeToOverriddenMethodsRector::class); + + $rectorConfig->phpVersion(PhpVersion::PHP_83); +}; diff --git a/rules/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector.php b/rules/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector.php new file mode 100644 index 00000000000..b8194e21fa1 --- /dev/null +++ b/rules/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector.php @@ -0,0 +1,143 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + // Detect if class extends a parent class + if ($this->shouldSkipClass($node)) { + return null; + } + + // Fetch the parent class reflection + $parentClassReflection = $this->reflectionProvider->getClass((string) $node->extends); + + $hasChanged = false; + + foreach ($node->getMethods() as $method) { + if ($method->name->toString() === '__construct') { + continue; + } + // Private methods should be ignored + if ($parentClassReflection->hasNativeMethod($method->name->toString())) { + // ignore if it is a private method on the parent + $parentMethod = $parentClassReflection->getNativeMethod($method->name->toString()); + if ($parentMethod->isPrivate()) { + continue; + } + // ignore if it already uses the attribute + if ($this->phpAttributeAnalyzer->hasPhpAttribute($method, 'Override')) { + continue; + } + $method->attrGroups[] = new AttributeGroup([new Attribute(new FullyQualified('Override'))]); + $hasChanged = true; + } + } + + if (! $hasChanged) { + return null; + } + + return $node; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::OVERRIDE_ATTRIBUTE; + } + + private function shouldSkipClass(Class_ $class): bool + { + if ($this->classAnalyzer->isAnonymousClass($class)) { + return true; + } + if (! $class->extends instanceof FullyQualified) { + return true; + } + + return ! $this->reflectionProvider->hasClass($class->extends->toString()); + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index c9048315e8b..bc41c89be98 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -643,4 +643,10 @@ final class PhpVersionFeature * @var int */ public const SENSITIVE_PARAMETER_ATTRIBUTE = PhpVersion::PHP_82; + + /** + * @see https://wiki.php.net/rfc/marking_overriden_methods + * @var int + */ + public const OVERRIDE_ATTRIBUTE = PhpVersion::PHP_83; }