206 lines
7.7 KiB
PHP
206 lines
7.7 KiB
PHP
<?php
|
|
/**
|
|
* PHPCompatibility, an external standard for PHP_CodeSniffer.
|
|
*
|
|
* @package PHPCompatibility
|
|
* @copyright 2012-2019 PHPCompatibility Contributors
|
|
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
|
|
* @link https://github.com/PHPCompatibility/PHPCompatibility
|
|
*/
|
|
|
|
namespace PHPCompatibility\Sniffs\FunctionNameRestrictions;
|
|
|
|
use Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff as PHPCS_CamelCapsFunctionNameSniff;
|
|
use PHP_CodeSniffer_File as File;
|
|
use PHP_CodeSniffer_Standards_AbstractScopeSniff as PHPCS_AbstractScopeSniff;
|
|
use PHP_CodeSniffer_Tokens as Tokens;
|
|
|
|
/**
|
|
* All function and method names starting with double underscore are reserved by PHP.
|
|
*
|
|
* PHP version All
|
|
*
|
|
* {@internal Extends an upstream sniff to benefit from the properties contained therein.
|
|
* The properties are lists of valid PHP magic function and method names, which
|
|
* should be ignored for the purposes of this sniff.
|
|
* As this sniff is not PHP version specific, we don't need access to the utility
|
|
* methods in the PHPCompatibility\Sniff, so extending the upstream sniff is fine.
|
|
* As the upstream sniff checks the same (and more, but we don't need the rest),
|
|
* the logic in this sniff is largely the same as used upstream.
|
|
* Extending the upstream sniff instead of including it via the ruleset, however,
|
|
* prevents hard to debug issues of errors not being reported from the upstream sniff
|
|
* if this library is used in combination with other rulesets.}
|
|
*
|
|
* @link https://www.php.net/manual/en/language.oop5.magic.php
|
|
*
|
|
* @since 8.2.0 This was previously, since 7.0.3, checked by the upstream sniff.
|
|
* @since 9.3.2 The sniff will now ignore functions marked as `@deprecated` by design.
|
|
*/
|
|
class ReservedFunctionNamesSniff extends PHPCS_CamelCapsFunctionNameSniff
|
|
{
|
|
|
|
/**
|
|
* Overload the constructor to work round various PHPCS cross-version compatibility issues.
|
|
*
|
|
* @since 8.2.0
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$scopeTokens = array(\T_CLASS, \T_INTERFACE, \T_TRAIT);
|
|
if (\defined('T_ANON_CLASS')) {
|
|
$scopeTokens[] = \T_ANON_CLASS;
|
|
}
|
|
|
|
// Call the grand-parent constructor directly.
|
|
PHPCS_AbstractScopeSniff::__construct($scopeTokens, array(\T_FUNCTION), true);
|
|
|
|
// Make sure debuginfo is included in the array. Upstream only includes it since 2.5.1.
|
|
$this->magicMethods['debuginfo'] = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Processes the tokens within the scope.
|
|
*
|
|
* @since 8.2.0
|
|
*
|
|
* @param \PHP_CodeSniffer_File $phpcsFile The file being processed.
|
|
* @param int $stackPtr The position where this token was
|
|
* found.
|
|
* @param int $currScope The position of the current scope.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope)
|
|
{
|
|
$tokens = $phpcsFile->getTokens();
|
|
|
|
/*
|
|
* Determine if this is a function which needs to be examined.
|
|
* The `processTokenWithinScope()` is called for each valid scope a method is in,
|
|
* so for nested classes, we need to make sure we only examine the token for
|
|
* the lowest level valid scope.
|
|
*/
|
|
$conditions = $tokens[$stackPtr]['conditions'];
|
|
end($conditions);
|
|
$deepestScope = key($conditions);
|
|
if ($deepestScope !== $currScope) {
|
|
return;
|
|
}
|
|
|
|
if ($this->isFunctionDeprecated($phpcsFile, $stackPtr) === true) {
|
|
/*
|
|
* Deprecated functions don't have to comply with the naming conventions,
|
|
* otherwise functions deprecated in favour of a function with a compliant
|
|
* name would still trigger an error.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
$methodName = $phpcsFile->getDeclarationName($stackPtr);
|
|
if ($methodName === null) {
|
|
// Ignore closures.
|
|
return;
|
|
}
|
|
|
|
// Is this a magic method. i.e., is prefixed with "__" ?
|
|
if (preg_match('|^__[^_]|', $methodName) > 0) {
|
|
$magicPart = strtolower(substr($methodName, 2));
|
|
if (isset($this->magicMethods[$magicPart]) === false
|
|
&& isset($this->methodsDoubleUnderscore[$magicPart]) === false
|
|
) {
|
|
$className = '[anonymous class]';
|
|
$scopeNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($currScope + 1), null, true);
|
|
if ($scopeNextNonEmpty !== false && $tokens[$scopeNextNonEmpty]['code'] === \T_STRING) {
|
|
$className = $tokens[$scopeNextNonEmpty]['content'];
|
|
}
|
|
|
|
$phpcsFile->addWarning(
|
|
'Method name "%s" is discouraged; PHP has reserved all method names with a double underscore prefix for future use.',
|
|
$stackPtr,
|
|
'MethodDoubleUnderscore',
|
|
array($className . '::' . $methodName)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Processes the tokens outside the scope.
|
|
*
|
|
* @since 8.2.0
|
|
*
|
|
* @param \PHP_CodeSniffer_File $phpcsFile The file being processed.
|
|
* @param int $stackPtr The position where this token was
|
|
* found.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
|
|
{
|
|
if ($this->isFunctionDeprecated($phpcsFile, $stackPtr) === true) {
|
|
/*
|
|
* Deprecated functions don't have to comply with the naming conventions,
|
|
* otherwise functions deprecated in favour of a function with a compliant
|
|
* name would still trigger an error.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
$functionName = $phpcsFile->getDeclarationName($stackPtr);
|
|
if ($functionName === null) {
|
|
// Ignore closures.
|
|
return;
|
|
}
|
|
|
|
// Is this a magic function. i.e., it is prefixed with "__".
|
|
if (preg_match('|^__[^_]|', $functionName) > 0) {
|
|
$magicPart = strtolower(substr($functionName, 2));
|
|
if (isset($this->magicFunctions[$magicPart]) === false) {
|
|
$phpcsFile->addWarning(
|
|
'Function name "%s" is discouraged; PHP has reserved all method names with a double underscore prefix for future use.',
|
|
$stackPtr,
|
|
'FunctionDoubleUnderscore',
|
|
array($functionName)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check whether a function has been marked as deprecated via a @deprecated tag
|
|
* in the function docblock.
|
|
*
|
|
* @since 9.3.2
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
|
|
* @param int $stackPtr The position of a T_FUNCTION
|
|
* token in the stack.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isFunctionDeprecated(File $phpcsFile, $stackPtr)
|
|
{
|
|
$tokens = $phpcsFile->getTokens();
|
|
$find = Tokens::$methodPrefixes;
|
|
$find[] = \T_WHITESPACE;
|
|
|
|
$commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
|
|
if ($tokens[$commentEnd]['code'] !== \T_DOC_COMMENT_CLOSE_TAG) {
|
|
// Function doesn't have a doc comment or is using the wrong type of comment.
|
|
return false;
|
|
}
|
|
|
|
$commentStart = $tokens[$commentEnd]['comment_opener'];
|
|
foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
|
|
if ($tokens[$tag]['content'] === '@deprecated') {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|