diff --git a/CHANGELOG.md b/CHANGELOG.md index d72efb7a..76eb9675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com). ## [Unreleased] +### Changed +- The `moodle.NamingConventions.ValidFunctionName` sniff will now ignore errors on methods employing the `#[\Override]` attribute. + ## [v3.4.9] - 2024-06-19 ### Fixed - Fixed a recent regression by allowing to the `moodle.Files.BoilerplateComment` sniff to contain "extra" consecutive comment lines immediately after the official boilerplate ends. diff --git a/moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php index a0b58f7a..9c1350b6 100644 --- a/moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -27,6 +27,7 @@ namespace MoodleHQ\MoodleCS\moodle\Sniffs\NamingConventions; +use MoodleHQ\MoodleCS\moodle\Util\Attributes; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Util\Tokens; @@ -108,6 +109,11 @@ protected function processTokenWithinScope(File $phpcsfile, $stackptr, $currscop $scope = $methodprops['scope']; $scopespecified = $methodprops['scope_specified']; + if (Attributes::hasOverrideAttribute($phpcsfile, $stackptr)) { + // This method has an `#[\Override]` attribute, so it is allowed to have a different name. + return; + } + // Only lower-case accepted. if ( preg_match('/[A-Z]+/', $methodname) && diff --git a/moodle/Tests/Util/AttributesTest.php b/moodle/Tests/Util/AttributesTest.php index 5351db82..6ab50796 100644 --- a/moodle/Tests/Util/AttributesTest.php +++ b/moodle/Tests/Util/AttributesTest.php @@ -184,4 +184,99 @@ function foo() {} $this->assertNull(Attributes::getAttributeProperties($phpcsFile, $searchPtr)); } + +/** + * @dataProvider hasOverrideAttributeProvider + */ + public function testHasOverrideAttribute( + string $content, + $stackPtrSearch, + bool $expected + ): void { + $config = new Config([]); + $ruleset = new Ruleset($config); + + $phpcsFile = new DummyFile($content, $ruleset, $config); + $phpcsFile->process(); + + $searchPtr = $phpcsFile->findNext($stackPtrSearch, 0); + + $this->assertEquals($expected, Attributes::hasOverrideAttribute($phpcsFile, $searchPtr)); + } + + public static function hasOverrideAttributeProvider(): array { + return [ + 'Not in a method' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + 'getTokens(); + $token = $tokens[$stackPtr]; + if ($token['code'] !== T_FUNCTION) { + // Not a function so can't have an Override Attribute. + return false; + } + + if (empty($token['conditions'])) { + // Not in a class or interface. + return false; + } + + $extendsOrImplements = false; + foreach ($token['conditions'] as $condition => $conditionCode) { + $extendsOrImplements = $extendsOrImplements || ObjectDeclarations::findExtendedClassName( + $phpcsFile, + $condition + ); + $extendsOrImplements = $extendsOrImplements || ObjectDeclarations::findImplementedInterfaceNames( + $phpcsFile, + $condition + ); + $extendsOrImplements = $extendsOrImplements || ObjectDeclarations::findExtendedInterfaceNames( + $phpcsFile, + $condition + ); + + if ($extendsOrImplements) { + break; + } + } + + if (!$extendsOrImplements) { + // The OVerride attrinbute can only apply to a class which has a parent. + return false; + } + + $attributes = self::getAttributePointers($phpcsFile, $stackPtr); + foreach ($attributes as $attributePtr) { + $attribute = self::getAttributeProperties($phpcsFile, $attributePtr); + if ($attribute['attribute_name'] === '\Override') { + return true; + } + } + + return false; + } }