Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore invalid method names using the Override attribute #170

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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;
}
}