Skip to content

Commit

Permalink
Ignore invalid method names using the Override attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Jun 19, 2024
1 parent 4feec2a commit 1e55422
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format of this change log follows the advice given at [Keep a CHANGELOG](htt
## [Unreleased]
### 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.
- The `moodle.NamingConventions.ValidFunctionName` sniff will now ignore errors on methods employing the `#[\Override]` attribute.

## [v3.4.8] - 2024-06-14
### Added
Expand Down
6 changes: 6 additions & 0 deletions moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) &&
Expand Down
95 changes: 95 additions & 0 deletions moodle/Tests/Util/AttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => [
'<?php
protected $example;
function exampleFunction(string $param): void {}',
T_PROPERTY,
false,
],
'Not in a class' => [
'<?php
function exampleFunction(string $param): void {}',
T_FUNCTION,
false,
],
'Not in a class, has Override' => [
'<?php
#[\Override]
function exampleFunction(string $param): void {}',
T_FUNCTION,
false
],
'In a class, no Override' => [
'<?php
class Example {
function exampleFunction(string $param): void {}
}',
T_FUNCTION,
false,
],
'In a class, does not extend/implement, has Override' => [
'<?php
class Example {
#[\Override]
function exampleFunction(string $param): void {}
}',
T_FUNCTION,
false,
],
'In a class, extends, no Override' => [
'<?php
class Example extends OtherExample {
function exampleFunction(string $param): void {}
}',
T_FUNCTION,
false,
],
'In a class, implements, no Override' => [
'<?php
class Example implements OtherExample {
function exampleFunction(string $param): void {}
}',
T_FUNCTION,
false,
],
'In a class, extends, has Override' => [
'<?php
class Example extends OtherExample {
#[\Override]
function exampleFunction(string $param): void {}
}',
T_FUNCTION,
true,
],
'In a class, implements, has Override' => [
'<?php
class Example implements OtherExample {
#[\Override]
function exampleFunction(string $param): void {}
}',
T_FUNCTION,
true,
],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ public function __call() {
echo 'hi';
}
};

class example extends class_with_correct_function_names {
#[\Override]
public function childMethod(): void {
echo 'hi';
}
}
62 changes: 62 additions & 0 deletions moodle/Util/Attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Utils\Context;
use PHPCSUtils\Utils\Namespaces;
use PHPCSUtils\Utils\ObjectDeclarations;

/**
* Utilities related to PHP Attributes.
Expand Down Expand Up @@ -108,4 +109,65 @@ public static function getAttributeProperties(

return $properties;
}

/**
* Check if a function has an \Override Attribute.
*
* Note: Override attributes can only be valid on methods of classes which extend or implement another class.
*
* @param File $phpcsFile
* @param int $stackPtr
* @return bool
*/
public static function hasOverrideAttribute(
File $phpcsFile,
int $stackPtr
): bool {
$tokens = $phpcsFile->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;
}
}

0 comments on commit 1e55422

Please sign in to comment.