Repository: phpmd/phpmd (php)

Issue:
Allow `\Override` attribute as inheritance declaration for method parameters
The behavior should be the same as the `@inheritdoc` annotation.

https://github.com/phpmd/phpmd/blob/caf04a5c1c91bdecefbe900c2a7d6235aa5c6106/src/main/php/PHPMD/Rule/UnusedFormalParameter.php#L101

See: https://www.php.net/manual/en/class.override.php.

Pull request:
Omit unused formal parameters when the method uses `\Override` attribute
Type: feature
Issue: Resolves #1192
Breaking change: no

<!--
Explain what the PR does and also why. If you have parts you are not sure about, please explain. 

Please check this points before submitting your PR.
 - Add test to cover the changes you made on the code.
 - If you have a change on the documentation, please link to the page that you change.
 - If you add a new feature please update the documentation in the same PR.
 - If you really need to add a breaking change, explain why it is needed. Understand that this result in a lower change to get the PR accepted.
 - Any PR need 2 approvals before it get merged, sometimes this can take some time. Please be patient.
  
 ## Adding a New Rule

- Add the new rule to the matching rule set XML, e.g. ``src/main/resources/rulesets/naming.xml``
- Add documentation for the new rule, e.g. ``src/site/rst/rules/naming.rst``
- Implement the new rule, e.g. ``src/main/php/PHPMD/Rule/Naming/LongVariable.php``
- Cover cases for the new rule in the rule test, e.g. ``src/test/php/PHPMD/Rule/Naming/LongVariableTest.php``
-- Cover the case when the new rule *should* apply
-- Cover the case when the new rule *should not* apply
-- Cover edge cases of the new rule

## Adding a New Rule Property

- Add the new property to rule set XML, e.g. ``src/main/resources/rulesets/naming.xml``
- Add documentation for the new property, e.g. ``src/site/rst/rules/naming.rst``
- Implement new property in rule, e.g. ``src/main/php/PHPMD/Rule/Naming/LongVariable.php``
- Cover cases for the new property in rule test, e.g. ``src/test/php/PHPMD/Rule/Naming/LongVariableTest.php``
-- Cover the case when the new property is not set and the rule *should not* apply
-- Cover the case when the new property is not set and the rule *should* apply
-- Cover case when the new property is set and the rule *should not* apply
-- Cover case when the new property is set and the rule *should* apply
-- Cover edge cases of the new property, if any
-->

## To Do
- [x] Check if there is a built-in AST to use instead of the reflection API; 
- [x] Add tests;
- [x] Wait for https://github.com/phpmd/phpmd/pull/1224.


Patch:
diff --git a/src/Rule/UnusedFormalParameter.php b/src/Rule/UnusedFormalParameter.php
index efce104b..d3d9f429 100644
--- a/src/Rule/UnusedFormalParameter.php
+++ b/src/Rule/UnusedFormalParameter.php
@@ -19,6 +19,7 @@
 namespace PHPMD\Rule;
 
 use OutOfBoundsException;
+use Override;
 use PDepend\Source\AST\AbstractASTCallable;
 use PDepend\Source\AST\ASTClassOrInterfaceRecursiveInheritanceException;
 use PDepend\Source\AST\ASTCompoundVariable;
@@ -32,6 +33,9 @@ use PDepend\Source\AST\ASTVariableDeclarator;
 use PHPMD\AbstractNode;
 use PHPMD\Node\AbstractCallableNode;
 use PHPMD\Node\MethodNode;
+use ReflectionException;
+use ReflectionMethod;
+use RuntimeException;
 
 /**
  * This rule collects all formal parameters of a given function or method that
@@ -51,6 +55,9 @@ final class UnusedFormalParameter extends AbstractLocalVariable implements Funct
     /**
      * This method checks that all parameters of a given function or method are
      * used at least one time within the artifacts body.
+     *
+     * @throws ReflectionException
+     * @throws RuntimeException
      */
     public function apply(AbstractNode $node): void
     {
@@ -112,16 +119,36 @@ final class UnusedFormalParameter extends AbstractLocalVariable implements Funct
 
     /**
      * Returns <b>true</b> when the given node is method with signature declared as inherited using
-     * {@inheritDoc} annotation.
+     * \Override attribute or the {@inheritDoc} annotation.
      *
      * @param AbstractCallableNode<AbstractASTCallable> $node
+     * @throws ReflectionException
+     * @throws RuntimeException
      */
     private function isInheritedSignature(AbstractCallableNode $node): bool
     {
-        if ($node instanceof MethodNode) {
-            $comment = $node->getComment();
+        if (!$node instanceof MethodNode) {
+            return false;
+        }
+
+        $comment = $node->getComment();
+
+        if ($comment && preg_match('/@inheritdoc/i', $comment)) {
+            return true;
+        }
+
+        if (\PHP_VERSION_ID < 80300 || !class_exists($node->getParentType()->getFullQualifiedName())) {
+            return false;
+        }
+
+        // Remove the "()" at the end of method's name.
+        $methodName = substr($node->getFullQualifiedName(), 0, -2);
+        $reflectionMethod = new ReflectionMethod($methodName);
 
-            return $comment && preg_match('/@inheritdoc/i', $comment);
+        foreach ($reflectionMethod->getAttributes() as $reflectionAttribute) {
+            if ($reflectionAttribute->getName() === Override::class) {
+                return true;
+            }
         }
 
         return false;


Test patch:
diff --git a/tests/php/PHPMD/Rule/UnusedFormalParameterTest.php b/tests/php/PHPMD/Rule/UnusedFormalParameterTest.php
index 12236e8e..03c3e988 100644
--- a/tests/php/PHPMD/Rule/UnusedFormalParameterTest.php
+++ b/tests/php/PHPMD/Rule/UnusedFormalParameterTest.php
@@ -481,4 +481,48 @@ class UnusedFormalParameterTest extends AbstractTestCase
         $rule->setReport($this->getReportWithNoViolation());
         $rule->apply($methods[0]);
     }
+
+    /**
+     * @requires PHP >= 8.3
+     */
+    public function testRuleDoesNotApplyToMethodWithOverrideAttribute(): void
+    {
+        if (\PHP_VERSION_ID < 80300) {
+            static::markTestSkipped('This test requires PHP >= 8.3 beceuase it uses the `\Override` PHP attribute.');
+        }
+
+        $rule = new UnusedFormalParameter();
+        $rule->setReport($this->getReportWithNoViolation());
+        $rule->apply($this->getMethod());
+    }
+
+    /**
+     * @requires PHP >= 8.3
+     */
+    public function testRuleAppliesToMethodWithoutOverrideAttribute(): void
+    {
+        if (\PHP_VERSION_ID < 80300) {
+            static::markTestSkipped('This test requires PHP >= 8.3 beceuase it uses the `\Override` PHP attribute.');
+        }
+
+        $rule = new UnusedFormalParameter();
+        $rule->setReport($this->getReportWithOneViolation());
+        $rule->apply($this->getMethod());
+    }
+
+    /**
+     * @requires PHP < 8.3
+     */
+    public function testRuleAppliesToMethodWithOverrideAttributeBeforePhp83(): void
+    {
+        if (\PHP_VERSION_ID >= 80300) {
+            static::markTestSkipped(
+                'This test requires PHP < 8.3 beceuase it checks the absence of the `\Override` PHP attribute.'
+            );
+        }
+
+        $rule = new UnusedFormalParameter();
+        $rule->setReport($this->getReportWithOneViolation());
+        $rule->apply($this->getMethod());
+    }
 }
diff --git a/tests/resources/files/Rule/UnusedFormalParameter/FooInterfaceWithOverride.php b/tests/resources/files/Rule/UnusedFormalParameter/FooInterfaceWithOverride.php
new file mode 100644
index 00000000..f62f905e
--- /dev/null
+++ b/tests/resources/files/Rule/UnusedFormalParameter/FooInterfaceWithOverride.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * This file is part of PHP Mess Detector.
+ *
+ * Copyright (c) Manuel Pichler <mapi@phpmd.org>.
+ * All rights reserved.
+ *
+ * Licensed under BSD License
+ * For full copyright and license information, please see the LICENSE file.
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author Manuel Pichler <mapi@phpmd.org>
+ * @copyright Manuel Pichler. All rights reserved.
+ * @license https://opensource.org/licenses/bsd-license.php BSD License
+ * @link http://phpmd.org/
+ */
+
+namespace files\Rule\UnusedFormalParameter;
+
+interface FooInterfaceWithOverride
+{
+    public function testRuleDoesNotApplyToMethodWithOverrideAttribute(bool $foo): void;
+}
diff --git a/tests/resources/files/Rule/UnusedFormalParameter/FooInterfaceWithoutOverride.php b/tests/resources/files/Rule/UnusedFormalParameter/FooInterfaceWithoutOverride.php
new file mode 100644
index 00000000..6bb94aa7
--- /dev/null
+++ b/tests/resources/files/Rule/UnusedFormalParameter/FooInterfaceWithoutOverride.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * This file is part of PHP Mess Detector.
+ *
+ * Copyright (c) Manuel Pichler <mapi@phpmd.org>.
+ * All rights reserved.
+ *
+ * Licensed under BSD License
+ * For full copyright and license information, please see the LICENSE file.
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author Manuel Pichler <mapi@phpmd.org>
+ * @copyright Manuel Pichler. All rights reserved.
+ * @license https://opensource.org/licenses/bsd-license.php BSD License
+ * @link http://phpmd.org/
+ */
+
+namespace files\Rule\UnusedFormalParameter;
+
+interface FooInterfaceWithoutOverride
+{
+    public function testRuleAppliesToMethodWithoutOverrideAttribute(bool $foo): void;
+}
diff --git a/tests/resources/files/Rule/UnusedFormalParameter/testRuleAppliesToMethodWithOverrideAttributeBeforePhp83.php b/tests/resources/files/Rule/UnusedFormalParameter/testRuleAppliesToMethodWithOverrideAttributeBeforePhp83.php
new file mode 100644
index 00000000..30b9cd6d
--- /dev/null
+++ b/tests/resources/files/Rule/UnusedFormalParameter/testRuleAppliesToMethodWithOverrideAttributeBeforePhp83.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * This file is part of PHP Mess Detector.
+ *
+ * Copyright (c) Manuel Pichler <mapi@phpmd.org>.
+ * All rights reserved.
+ *
+ * Licensed under BSD License
+ * For full copyright and license information, please see the LICENSE file.
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author Manuel Pichler <mapi@phpmd.org>
+ * @copyright Manuel Pichler. All rights reserved.
+ * @license https://opensource.org/licenses/bsd-license.php BSD License
+ * @link http://phpmd.org/
+ */
+
+
+namespace files\Rule\UnusedFormalParameter;
+
+final class testRuleAppliesToMethodWithOverrideAttributeBeforePhp83
+{
+    #[\Override]
+    public function testRuleAppliesToMethodWithOverrideAttributeBeforePhp83($foo): void
+    {
+    }
+}
diff --git a/tests/resources/files/Rule/UnusedFormalParameter/testRuleAppliesToMethodWithoutOverrideAttribute.php b/tests/resources/files/Rule/UnusedFormalParameter/testRuleAppliesToMethodWithoutOverrideAttribute.php
new file mode 100644
index 00000000..439315d9
--- /dev/null
+++ b/tests/resources/files/Rule/UnusedFormalParameter/testRuleAppliesToMethodWithoutOverrideAttribute.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * This file is part of PHP Mess Detector.
+ *
+ * Copyright (c) Manuel Pichler <mapi@phpmd.org>.
+ * All rights reserved.
+ *
+ * Licensed under BSD License
+ * For full copyright and license information, please see the LICENSE file.
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author Manuel Pichler <mapi@phpmd.org>
+ * @copyright Manuel Pichler. All rights reserved.
+ * @license https://opensource.org/licenses/bsd-license.php BSD License
+ * @link http://phpmd.org/
+ */
+
+namespace files\Rule\UnusedFormalParameter;
+
+final class testRuleAppliesToMethodWithoutOverrideAttribute implements FooInterfaceWithoutOverride
+{
+    public function testRuleAppliesToMethodWithoutOverrideAttribute(bool $foo): void
+    {
+    }
+}
diff --git a/tests/resources/files/Rule/UnusedFormalParameter/testRuleDoesNotApplyToMethodWithOverrideAttribute.php b/tests/resources/files/Rule/UnusedFormalParameter/testRuleDoesNotApplyToMethodWithOverrideAttribute.php
new file mode 100644
index 00000000..2e80a74b
--- /dev/null
+++ b/tests/resources/files/Rule/UnusedFormalParameter/testRuleDoesNotApplyToMethodWithOverrideAttribute.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * This file is part of PHP Mess Detector.
+ *
+ * Copyright (c) Manuel Pichler <mapi@phpmd.org>.
+ * All rights reserved.
+ *
+ * Licensed under BSD License
+ * For full copyright and license information, please see the LICENSE file.
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author Manuel Pichler <mapi@phpmd.org>
+ * @copyright Manuel Pichler. All rights reserved.
+ * @license https://opensource.org/licenses/bsd-license.php BSD License
+ * @link http://phpmd.org/
+ */
+
+namespace files\Rule\UnusedFormalParameter;
+
+final class testRuleDoesNotApplyToMethodWithOverrideAttribute implements FooInterfaceWithOverride
+{
+    #[\Override]
+    public function testRuleDoesNotApplyToMethodWithOverrideAttribute(bool $foo): void
+    {
+    }
+}