-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #243 from PHPCSStandards/feature/new-final-methods…
…-in-traits-sniff ✨ New `Universal.OOStructures.RequireFinalMethodsInTraits` sniff
- Loading branch information
Showing
5 changed files
with
473 additions
and
0 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
Universal/Docs/OOStructures/RequireFinalMethodsInTraitsStandard.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?xml version="1.0"?> | ||
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd" | ||
title="Require Final Methods in Traits" | ||
> | ||
<standard> | ||
<![CDATA[ | ||
Requires the use of the `final` keyword for non-abstract, non-private methods in traits. | ||
By default, magic methods are exempt from this check. Magic methods can be enforced to be `final` too by setting the `includeMagicMethods` property to `true`. | ||
]]> | ||
</standard> | ||
<code_comparison> | ||
<code title="Valid: Final methods in a trait."> | ||
<![CDATA[ | ||
trait Foo { | ||
<em>final</em> public function bar() {} | ||
<em>final</em> public static function baz() {} | ||
// Also valid (out of scope): | ||
protected abstract function overload() {} | ||
private function okay() {} | ||
} | ||
]]> | ||
</code> | ||
<code title="Invalid: Non-final methods in a trait."> | ||
<![CDATA[ | ||
trait Foo { | ||
<em>public function</em> bar() {} | ||
<em>protected static function</em> baz() {} | ||
} | ||
]]> | ||
</code> | ||
</code_comparison> | ||
</documentation> |
129 changes: 129 additions & 0 deletions
129
Universal/Sniffs/OOStructures/RequireFinalMethodsInTraitsSniff.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
<?php | ||
/** | ||
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer. | ||
* | ||
* @package PHPCSExtra | ||
* @copyright 2023 PHPCSExtra Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSExtra | ||
*/ | ||
|
||
namespace PHPCSExtra\Universal\Sniffs\OOStructures; | ||
|
||
use PHP_CodeSniffer\Files\File; | ||
use PHP_CodeSniffer\Sniffs\Sniff; | ||
use PHPCSUtils\Utils\FunctionDeclarations; | ||
use PHPCSUtils\Utils\ObjectDeclarations; | ||
use PHPCSUtils\Utils\Scopes; | ||
|
||
/** | ||
* Require non-abstract, non-private methods in traits to be declared as "final". | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
final class RequireFinalMethodsInTraitsSniff implements Sniff | ||
{ | ||
|
||
/** | ||
* Name of the metric. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @var string | ||
*/ | ||
const METRIC_NAME = 'Non-private method in trait is abstract or final ?'; | ||
|
||
/** | ||
* Whether or not this rule applies to magic methods. | ||
* | ||
* Defaults to `false`. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @var bool | ||
*/ | ||
public $includeMagicMethods = false; | ||
|
||
/** | ||
* Returns an array of tokens this test wants to listen for. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @return array | ||
*/ | ||
public function register() | ||
{ | ||
return [\T_FUNCTION]; | ||
} | ||
|
||
/** | ||
* Processes this test, when one of its tokens is encountered. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. | ||
* @param int $stackPtr The position of the current token | ||
* in the stack passed in $tokens. | ||
* | ||
* @return void | ||
*/ | ||
public function process(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) { | ||
// Parse error/live coding. | ||
return; | ||
} | ||
|
||
$scopePtr = Scopes::validDirectScope($phpcsFile, $stackPtr, \T_TRAIT); | ||
if ($scopePtr === false) { | ||
// Not a trait method. | ||
return; | ||
} | ||
|
||
$methodName = FunctionDeclarations::getName($phpcsFile, $stackPtr); | ||
if ($this->includeMagicMethods === false | ||
&& FunctionDeclarations::isMagicMethodName($methodName) === true | ||
) { | ||
// Magic methods are excluded. Bow out. | ||
return; | ||
} | ||
|
||
$methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); | ||
if ($methodProps['scope'] === 'private') { | ||
// Private methods can't be final. | ||
return; | ||
} | ||
|
||
if ($methodProps['is_final'] === true) { | ||
// Already final, nothing to do. | ||
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); | ||
return; | ||
} | ||
|
||
if ($methodProps['is_abstract'] === true) { | ||
// Abstract classes can't be final. | ||
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); | ||
return; | ||
} | ||
|
||
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); | ||
|
||
$data = [ | ||
$methodProps['scope'], | ||
$methodName, | ||
ObjectDeclarations::getName($phpcsFile, $scopePtr), | ||
]; | ||
|
||
$fix = $phpcsFile->addFixableError( | ||
'The non-abstract, %s method "%s()" in trait %s should be declared as final.', | ||
$stackPtr, | ||
'NonFinalMethodFound', | ||
$data | ||
); | ||
|
||
if ($fix === true) { | ||
$phpcsFile->fixer->addContentBefore($stackPtr, 'final '); | ||
} | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
Universal/Tests/OOStructures/RequireFinalMethodsInTraitsUnitTest.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
<?php | ||
|
||
/* | ||
* OK. | ||
*/ | ||
function NotOurTarget() {} | ||
|
||
class FooA { | ||
protected function NotOurTarget() {} | ||
} | ||
|
||
interface FooB { | ||
function NotOurTarget(); | ||
} | ||
|
||
enum FooC { | ||
public function NotOurTarget(); | ||
} | ||
|
||
$anon = new class { | ||
function anonClassIsFinalByNature() {} | ||
} | ||
|
||
trait MethodsAreAlreadyFinal { | ||
final public function publicFinalMethod(); | ||
protected final function protectedFinalMethod() {} | ||
} | ||
|
||
trait TheseAreExempt { | ||
public abstract function publicAbstractMethodShouldNotBeFinal(); | ||
abstract protected function protectedAbstractMethodShouldNotBeFinal() {} | ||
|
||
private function privateMethodsCannotBeFinal(); | ||
|
||
public function parseError | ||
} | ||
|
||
trait FinalMagicMethodsAreNotFlagged { | ||
final public function __construct() {} | ||
public final function __destruct() {} | ||
public final function __clone() {} | ||
final public function __debugInfo() {} | ||
final public function __invoke() {} | ||
public final function __get($name) {} | ||
public final function __set($name, $value) {} | ||
final public function __isset($name) {} | ||
final public function __unset($name) {} | ||
public final function __call($name, $arguments) {} | ||
public static final function __callStatic($name, $arguments) {} | ||
final public function __sleep() {} | ||
public final function __toString() {} | ||
public final static function __set_state($properties) {} | ||
final public function __serialize() {} | ||
final public function __unserialize($data) {} | ||
} | ||
|
||
trait MagicMethodsAreNotFlaggedByDefault { | ||
public function __construct() {} | ||
public function __destruct() {} | ||
public function __clone() {} | ||
public function __debugInfo() {} | ||
public function __invoke() {} | ||
public function __get($name) {} | ||
public function __set($name, $value) {} | ||
public function __isset($name) {} | ||
public function __unset($name) {} | ||
public function __call($name, $arguments) {} | ||
public static function __callStatic($name, $arguments) {} | ||
public function __sleep() {} | ||
public function __toString() {} | ||
public static function __set_state($properties) {} | ||
public function __serialize() {} | ||
public function __unserialize($data) {} | ||
} | ||
|
||
|
||
/* | ||
* Bad. | ||
*/ | ||
trait FixMe { | ||
public function publicMethod() {} | ||
protected function protectedMethod() {} | ||
|
||
public static function publicStaticMethod() {} | ||
static protected function protectedStaticMethod() {} | ||
|
||
protected function __doubleUnderscoresButNotMagic() {} | ||
|
||
/** | ||
* Docblock | ||
*/ | ||
public function withDocblock() {} | ||
|
||
static /*comment*/ protected function withCommentBetweenKeywords() {} | ||
|
||
/*comment*/ protected function withCommentBeforeKeyword() {} | ||
} | ||
|
||
// phpcs:set Universal.OOStructures.RequireFinalMethodsInTraits includeMagicMethods true | ||
trait MagicMethodsAreFlaggedOnRequest { | ||
public function __construct() {} | ||
public function __destruct() {} | ||
public function __clone() {} | ||
public function __debugInfo() {} | ||
public function __invoke() {} | ||
public function __get($name) {} | ||
public function __set($name, $value) {} | ||
public function __isset($name) {} | ||
public function __unset($name) {} | ||
public function __call($name, $arguments) {} | ||
public static function __callStatic($name, $arguments) {} | ||
public function __sleep() {} | ||
public function __toString() {} | ||
public static function __set_state($properties) {} | ||
public function __serialize() {} | ||
public function __unserialize($data) {} | ||
} | ||
|
||
// Reset property to default value. | ||
// phpcs:set Universal.OOStructures.RequireFinalMethodsInTraits includeMagicMethods false |
Oops, something went wrong.