341 lines
13 KiB
PHP
341 lines
13 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
|
||
|
*
|
||
|
* @package PHPCSUtils
|
||
|
* @copyright 2019-2020 PHPCSUtils Contributors
|
||
|
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
|
||
|
* @link https://github.com/PHPCSStandards/PHPCSUtils
|
||
|
*/
|
||
|
|
||
|
namespace PHPCSUtils\Utils;
|
||
|
|
||
|
use PHP_CodeSniffer\Exceptions\RuntimeException;
|
||
|
use PHP_CodeSniffer\Files\File;
|
||
|
use PHP_CodeSniffer\Util\Tokens;
|
||
|
use PHPCSUtils\Internal\Cache;
|
||
|
use PHPCSUtils\Tokens\Collections;
|
||
|
use PHPCSUtils\Utils\Scopes;
|
||
|
use PHPCSUtils\Utils\TextStrings;
|
||
|
|
||
|
/**
|
||
|
* Utility functions for use when examining variables.
|
||
|
*
|
||
|
* @since 1.0.0 The `Variables::getMemberProperties()` method is based on and inspired by
|
||
|
* the method of the same name in the PHPCS native `PHP_CodeSniffer\Files\File` class.
|
||
|
* Also see {@see \PHPCSUtils\BackCompat\BCFile}.
|
||
|
*/
|
||
|
final class Variables
|
||
|
{
|
||
|
|
||
|
/**
|
||
|
* List of PHP Reserved variables.
|
||
|
*
|
||
|
* The array keys are the variable names without the leading dollar sign, the values indicate
|
||
|
* whether the variable is a superglobal or not.
|
||
|
*
|
||
|
* The variables names are set without the leading dollar sign to allow this array
|
||
|
* to be used with array index keys as well. Think: `'_GET'` in `$GLOBALS['_GET']`.}
|
||
|
*
|
||
|
* @link https://php.net/reserved.variables PHP Manual on reserved variables
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @var array <string> => <bool>
|
||
|
*/
|
||
|
public static $phpReservedVars = [
|
||
|
'_SERVER' => true,
|
||
|
'_GET' => true,
|
||
|
'_POST' => true,
|
||
|
'_REQUEST' => true,
|
||
|
'_SESSION' => true,
|
||
|
'_ENV' => true,
|
||
|
'_COOKIE' => true,
|
||
|
'_FILES' => true,
|
||
|
'GLOBALS' => true,
|
||
|
'http_response_header' => false,
|
||
|
'argc' => false,
|
||
|
'argv' => false,
|
||
|
|
||
|
// Deprecated.
|
||
|
'php_errormsg' => false,
|
||
|
|
||
|
// Removed PHP 5.4.0.
|
||
|
'HTTP_SERVER_VARS' => false,
|
||
|
'HTTP_GET_VARS' => false,
|
||
|
'HTTP_POST_VARS' => false,
|
||
|
'HTTP_SESSION_VARS' => false,
|
||
|
'HTTP_ENV_VARS' => false,
|
||
|
'HTTP_COOKIE_VARS' => false,
|
||
|
'HTTP_POST_FILES' => false,
|
||
|
|
||
|
// Removed PHP 5.6.0.
|
||
|
'HTTP_RAW_POST_DATA' => false,
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* Retrieve the visibility and implementation properties of a class member variable.
|
||
|
*
|
||
|
* Main differences with the PHPCS version:
|
||
|
* - Removed the parse error warning for properties in interfaces.
|
||
|
* This will now throw the same _"$stackPtr is not a class member var"_ runtime exception as
|
||
|
* other non-property variables passed to the method.
|
||
|
* - Defensive coding against incorrect calls to this method.
|
||
|
* - Support PHP 8.0 identifier name tokens in property types, cross-version PHP & PHPCS.
|
||
|
* - Support for the PHP 8.2 `true` type.
|
||
|
* - The results of this function call are cached during a PHPCS run for faster response times.
|
||
|
*
|
||
|
* @see \PHP_CodeSniffer\Files\File::getMemberProperties() Original source.
|
||
|
* @see \PHPCSUtils\BackCompat\BCFile::getMemberProperties() Cross-version compatible version of the original.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
|
||
|
* @param int $stackPtr The position in the stack of the `T_VARIABLE` token
|
||
|
* to acquire the properties for.
|
||
|
*
|
||
|
* @return array Array with information about the class member variable.
|
||
|
* The format of the return value is:
|
||
|
* ```php
|
||
|
* array(
|
||
|
* 'scope' => string, // Public, private, or protected.
|
||
|
* 'scope_specified' => boolean, // TRUE if the scope was explicitly specified.
|
||
|
* 'is_static' => boolean, // TRUE if the static keyword was found.
|
||
|
* 'is_readonly' => boolean, // TRUE if the readonly keyword was found.
|
||
|
* 'type' => string, // The type of the var (empty if no type specified).
|
||
|
* 'type_token' => integer|false, // The stack pointer to the start of the type
|
||
|
* // or FALSE if there is no type.
|
||
|
* 'type_end_token' => integer|false, // The stack pointer to the end of the type
|
||
|
* // or FALSE if there is no type.
|
||
|
* 'nullable_type' => boolean, // TRUE if the type is preceded by the
|
||
|
* // nullability operator.
|
||
|
* );
|
||
|
* ```
|
||
|
*
|
||
|
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a
|
||
|
* `T_VARIABLE` token.
|
||
|
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a
|
||
|
* class member variable.
|
||
|
*/
|
||
|
public static function getMemberProperties(File $phpcsFile, $stackPtr)
|
||
|
{
|
||
|
$tokens = $phpcsFile->getTokens();
|
||
|
|
||
|
if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_VARIABLE) {
|
||
|
throw new RuntimeException('$stackPtr must be of type T_VARIABLE');
|
||
|
}
|
||
|
|
||
|
if (Scopes::isOOProperty($phpcsFile, $stackPtr) === false) {
|
||
|
throw new RuntimeException('$stackPtr is not a class member var');
|
||
|
}
|
||
|
|
||
|
if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) {
|
||
|
return Cache::get($phpcsFile, __METHOD__, $stackPtr);
|
||
|
}
|
||
|
|
||
|
$valid = Collections::propertyModifierKeywords() + Tokens::$emptyTokens;
|
||
|
|
||
|
$scope = 'public';
|
||
|
$scopeSpecified = false;
|
||
|
$isStatic = false;
|
||
|
$isReadonly = false;
|
||
|
|
||
|
$startOfStatement = $phpcsFile->findPrevious(
|
||
|
[
|
||
|
\T_SEMICOLON,
|
||
|
\T_OPEN_CURLY_BRACKET,
|
||
|
\T_CLOSE_CURLY_BRACKET,
|
||
|
\T_ATTRIBUTE_END,
|
||
|
],
|
||
|
($stackPtr - 1)
|
||
|
);
|
||
|
|
||
|
for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
|
||
|
if (isset($valid[$tokens[$i]['code']]) === false) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch ($tokens[$i]['code']) {
|
||
|
case \T_PUBLIC:
|
||
|
$scope = 'public';
|
||
|
$scopeSpecified = true;
|
||
|
break;
|
||
|
case \T_PRIVATE:
|
||
|
$scope = 'private';
|
||
|
$scopeSpecified = true;
|
||
|
break;
|
||
|
case \T_PROTECTED:
|
||
|
$scope = 'protected';
|
||
|
$scopeSpecified = true;
|
||
|
break;
|
||
|
case \T_STATIC:
|
||
|
$isStatic = true;
|
||
|
break;
|
||
|
case \T_READONLY:
|
||
|
$isReadonly = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$type = '';
|
||
|
$typeToken = false;
|
||
|
$typeEndToken = false;
|
||
|
$nullableType = false;
|
||
|
$propertyTypeTokens = Collections::propertyTypeTokens();
|
||
|
|
||
|
/*
|
||
|
* BC PHPCS < 3.x.x: The union type separator is not (yet) retokenized correctly
|
||
|
* for union types containing the `true` type.
|
||
|
*/
|
||
|
$propertyTypeTokens[\T_BITWISE_OR] = \T_BITWISE_OR;
|
||
|
|
||
|
if ($i < $stackPtr) {
|
||
|
// We've found a type.
|
||
|
for ($i; $i < $stackPtr; $i++) {
|
||
|
if ($tokens[$i]['code'] === \T_VARIABLE) {
|
||
|
// Hit another variable in a group definition.
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($tokens[$i]['code'] === \T_NULLABLE) {
|
||
|
$nullableType = true;
|
||
|
}
|
||
|
|
||
|
if (isset($propertyTypeTokens[$tokens[$i]['code']]) === true) {
|
||
|
$typeEndToken = $i;
|
||
|
if ($typeToken === false) {
|
||
|
$typeToken = $i;
|
||
|
}
|
||
|
|
||
|
$type .= $tokens[$i]['content'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($type !== '' && $nullableType === true) {
|
||
|
$type = '?' . $type;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$returnValue = [
|
||
|
'scope' => $scope,
|
||
|
'scope_specified' => $scopeSpecified,
|
||
|
'is_static' => $isStatic,
|
||
|
'is_readonly' => $isReadonly,
|
||
|
'type' => $type,
|
||
|
'type_token' => $typeToken,
|
||
|
'type_end_token' => $typeEndToken,
|
||
|
'nullable_type' => $nullableType,
|
||
|
];
|
||
|
|
||
|
Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue);
|
||
|
return $returnValue;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verify if a given variable name is the name of a PHP reserved variable.
|
||
|
*
|
||
|
* @see \PHPCSUtils\Utils\Variables::$phpReservedVars List of variables names reserved by PHP.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param string $name The full variable name with or without leading dollar sign.
|
||
|
* This allows for passing an array key variable name, such as
|
||
|
* `'_GET'` retrieved from `$GLOBALS['_GET']`.
|
||
|
* > Note: when passing an array key, string quotes are expected
|
||
|
* to have been stripped already.
|
||
|
* Also see: {@see \PHPCSUtils\Utils\TextStrings::stripQuotes()}.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function isPHPReservedVarName($name)
|
||
|
{
|
||
|
if (\strpos($name, '$') === 0) {
|
||
|
$name = \substr($name, 1);
|
||
|
}
|
||
|
|
||
|
return (isset(self::$phpReservedVars[$name]) === true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verify if a given variable or array key token points to a PHP superglobal.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
|
||
|
* @param int $stackPtr The position in the stack of a `T_VARIABLE`
|
||
|
* token or of the `T_CONSTANT_ENCAPSED_STRING`
|
||
|
* array key to a variable in `$GLOBALS`.
|
||
|
*
|
||
|
* @return bool `TRUE` if this points to a superglobal; `FALSE` when not.
|
||
|
* > Note: This includes returning `FALSE` when an unsupported token has
|
||
|
* been passed, when a `T_CONSTANT_ENCAPSED_STRING` has been passed which
|
||
|
* is not an array index key; or when it is, but is not an index to the
|
||
|
* `$GLOBALS` variable.
|
||
|
*/
|
||
|
public static function isSuperglobal(File $phpcsFile, $stackPtr)
|
||
|
{
|
||
|
$tokens = $phpcsFile->getTokens();
|
||
|
|
||
|
if (isset($tokens[$stackPtr]) === false
|
||
|
|| ($tokens[$stackPtr]['code'] !== \T_VARIABLE
|
||
|
&& $tokens[$stackPtr]['code'] !== \T_CONSTANT_ENCAPSED_STRING)
|
||
|
) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$content = $tokens[$stackPtr]['content'];
|
||
|
|
||
|
if ($tokens[$stackPtr]['code'] === \T_CONSTANT_ENCAPSED_STRING) {
|
||
|
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
|
||
|
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
|
||
|
if (($prev === false || $tokens[$prev]['code'] !== \T_OPEN_SQUARE_BRACKET)
|
||
|
|| ($next === false || $tokens[$next]['code'] !== \T_CLOSE_SQUARE_BRACKET)
|
||
|
) {
|
||
|
// Not a single string array index key.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$pprev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
|
||
|
if ($pprev === false
|
||
|
|| $tokens[$pprev]['code'] !== \T_VARIABLE
|
||
|
|| $tokens[$pprev]['content'] !== '$GLOBALS'
|
||
|
) {
|
||
|
// Not accessing the `$GLOBALS` array.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Strip quotes.
|
||
|
$content = TextStrings::stripQuotes($content);
|
||
|
}
|
||
|
|
||
|
return self::isSuperglobalName($content);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verify if a given variable name is the name of a PHP superglobal.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param string $name The full variable name with or without leading dollar sign.
|
||
|
* This allows for passing an array key variable name, such as
|
||
|
* `'_GET'` retrieved from `$GLOBALS['_GET']`.
|
||
|
* > Note: when passing an array key, string quotes are expected
|
||
|
* to have been stripped already.
|
||
|
* Also see: {@see \PHPCSUtils\Utils\TextStrings::stripQuotes()}.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function isSuperglobalName($name)
|
||
|
{
|
||
|
if (\strpos($name, '$') === 0) {
|
||
|
$name = \substr($name, 1);
|
||
|
}
|
||
|
|
||
|
if (isset(self::$phpReservedVars[$name]) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return self::$phpReservedVars[$name];
|
||
|
}
|
||
|
}
|