Skip to content

Commit e92f166

Browse files
authored
Merge pull request #170 from andrewnicols/ignoreFunctionCasingIfOverridden
Ignore invalid method names using the Override attribute
2 parents 44ad668 + 48f8099 commit e92f166

File tree

5 files changed

+173
-0
lines changed

5 files changed

+173
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66
The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com).
77

88
## [Unreleased]
9+
### Changed
10+
- The `moodle.NamingConventions.ValidFunctionName` sniff will now ignore errors on methods employing the `#[\Override]` attribute.
11+
912
## [v3.4.9] - 2024-06-19
1013
### Fixed
1114
- Fixed a recent regression by allowing to the `moodle.Files.BoilerplateComment` sniff to contain "extra" consecutive comment lines immediately after the official boilerplate ends.

moodle/Sniffs/NamingConventions/ValidFunctionNameSniff.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
namespace MoodleHQ\MoodleCS\moodle\Sniffs\NamingConventions;
2929

30+
use MoodleHQ\MoodleCS\moodle\Util\Attributes;
3031
use PHP_CodeSniffer\Files\File;
3132
use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
3233
use PHP_CodeSniffer\Util\Tokens;
@@ -108,6 +109,11 @@ protected function processTokenWithinScope(File $phpcsfile, $stackptr, $currscop
108109
$scope = $methodprops['scope'];
109110
$scopespecified = $methodprops['scope_specified'];
110111

112+
if (Attributes::hasOverrideAttribute($phpcsfile, $stackptr)) {
113+
// This method has an `#[\Override]` attribute, so it is allowed to have a different name.
114+
return;
115+
}
116+
111117
// Only lower-case accepted.
112118
if (
113119
preg_match('/[A-Z]+/', $methodname) &&

moodle/Tests/Util/AttributesTest.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,99 @@ function foo() {}
184184

185185
$this->assertNull(Attributes::getAttributeProperties($phpcsFile, $searchPtr));
186186
}
187+
188+
/**
189+
* @dataProvider hasOverrideAttributeProvider
190+
*/
191+
public function testHasOverrideAttribute(
192+
string $content,
193+
$stackPtrSearch,
194+
bool $expected
195+
): void {
196+
$config = new Config([]);
197+
$ruleset = new Ruleset($config);
198+
199+
$phpcsFile = new DummyFile($content, $ruleset, $config);
200+
$phpcsFile->process();
201+
202+
$searchPtr = $phpcsFile->findNext($stackPtrSearch, 0);
203+
204+
$this->assertEquals($expected, Attributes::hasOverrideAttribute($phpcsFile, $searchPtr));
205+
}
206+
207+
public static function hasOverrideAttributeProvider(): array {
208+
return [
209+
'Not in a method' => [
210+
'<?php
211+
protected $example;
212+
function exampleFunction(string $param): void {}',
213+
T_PROPERTY,
214+
false,
215+
],
216+
'Not in a class' => [
217+
'<?php
218+
function exampleFunction(string $param): void {}',
219+
T_FUNCTION,
220+
false,
221+
],
222+
'Not in a class, has Override' => [
223+
'<?php
224+
#[\Override]
225+
function exampleFunction(string $param): void {}',
226+
T_FUNCTION,
227+
false,
228+
],
229+
'In a class, no Override' => [
230+
'<?php
231+
class Example {
232+
function exampleFunction(string $param): void {}
233+
}',
234+
T_FUNCTION,
235+
false,
236+
],
237+
'In a class, does not extend/implement, has Override' => [
238+
'<?php
239+
class Example {
240+
#[\Override]
241+
function exampleFunction(string $param): void {}
242+
}',
243+
T_FUNCTION,
244+
false,
245+
],
246+
'In a class, extends, no Override' => [
247+
'<?php
248+
class Example extends OtherExample {
249+
function exampleFunction(string $param): void {}
250+
}',
251+
T_FUNCTION,
252+
false,
253+
],
254+
'In a class, implements, no Override' => [
255+
'<?php
256+
class Example implements OtherExample {
257+
function exampleFunction(string $param): void {}
258+
}',
259+
T_FUNCTION,
260+
false,
261+
],
262+
'In a class, extends, has Override' => [
263+
'<?php
264+
class Example extends OtherExample {
265+
#[\Override]
266+
function exampleFunction(string $param): void {}
267+
}',
268+
T_FUNCTION,
269+
true,
270+
],
271+
'In a class, implements, has Override' => [
272+
'<?php
273+
class Example implements OtherExample {
274+
#[\Override]
275+
function exampleFunction(string $param): void {}
276+
}',
277+
T_FUNCTION,
278+
true,
279+
],
280+
];
281+
}
187282
}

moodle/Tests/fixtures/namingconventions/validfunctionname_correct.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,10 @@ public function __call() {
6464
echo 'hi';
6565
}
6666
};
67+
68+
class example extends class_with_correct_function_names {
69+
#[\Override]
70+
public function childMethod(): void {
71+
echo 'hi';
72+
}
73+
}

moodle/Util/Attributes.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PHP_CodeSniffer\Files\File;
2121
use PHPCSUtils\Utils\Context;
2222
use PHPCSUtils\Utils\Namespaces;
23+
use PHPCSUtils\Utils\ObjectDeclarations;
2324

2425
/**
2526
* Utilities related to PHP Attributes.
@@ -108,4 +109,65 @@ public static function getAttributeProperties(
108109

109110
return $properties;
110111
}
112+
113+
/**
114+
* Check if a function has an \Override Attribute.
115+
*
116+
* Note: Override attributes can only be valid on methods of classes which extend or implement another class.
117+
*
118+
* @param File $phpcsFile
119+
* @param int $stackPtr
120+
* @return bool
121+
*/
122+
public static function hasOverrideAttribute(
123+
File $phpcsFile,
124+
int $stackPtr
125+
): bool {
126+
$tokens = $phpcsFile->getTokens();
127+
$token = $tokens[$stackPtr];
128+
if ($token['code'] !== T_FUNCTION) {
129+
// Not a function so can't have an Override Attribute.
130+
return false;
131+
}
132+
133+
if (empty($token['conditions'])) {
134+
// Not in a class or interface.
135+
return false;
136+
}
137+
138+
$extendsOrImplements = false;
139+
foreach ($token['conditions'] as $condition => $conditionCode) {
140+
$extendsOrImplements = $extendsOrImplements || ObjectDeclarations::findExtendedClassName(
141+
$phpcsFile,
142+
$condition
143+
);
144+
$extendsOrImplements = $extendsOrImplements || ObjectDeclarations::findImplementedInterfaceNames(
145+
$phpcsFile,
146+
$condition
147+
);
148+
$extendsOrImplements = $extendsOrImplements || ObjectDeclarations::findExtendedInterfaceNames(
149+
$phpcsFile,
150+
$condition
151+
);
152+
153+
if ($extendsOrImplements) {
154+
break;
155+
}
156+
}
157+
158+
if (!$extendsOrImplements) {
159+
// The OVerride attrinbute can only apply to a class which has a parent.
160+
return false;
161+
}
162+
163+
$attributes = self::getAttributePointers($phpcsFile, $stackPtr);
164+
foreach ($attributes as $attributePtr) {
165+
$attribute = self::getAttributeProperties($phpcsFile, $attributePtr);
166+
if ($attribute['attribute_name'] === '\Override') {
167+
return true;
168+
}
169+
}
170+
171+
return false;
172+
}
111173
}

0 commit comments

Comments
 (0)