xarxaprod-wp-theme/vendor/phpcompatibility/php-compatibility/PHPCompatibility/Sniffs/Variables/ForbiddenThisUseContextsSni...

426 lines
14 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\Variables;
use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* Detect using `$this` in incompatible contexts.
*
* "Whilst `$this` is considered a special variable in PHP, it lacked proper checks
* to ensure it wasn't used as a variable name or reassigned. This has now been
* rectified to ensure that `$this` cannot be a user-defined variable, reassigned
* to a different value, or be globalised."
*
* This sniff only addresses those situations which did *not* throw an error prior
* to PHP 7.1, either at all or only in PHP 7.0.
* In other words, the following situation, while mentioned in the RFC, will NOT
* be sniffed for:
* - Using $this as static variable. (error _message_ change only).
*
* Also, the changes with relation to assigning `$this` dynamically can not be
* sniffed for reliably, so are not covered by this sniff.
* - Disable ability to re-assign `$this` indirectly through `$$`.
* - Disable ability to re-assign `$this` indirectly through reference.
* - Disable ability to re-assign `$this` indirectly through `extract()` and `parse_str()`.
*
* Other changes not (yet) covered:
* - `get_defined_vars()` always doesn't show value of variable `$this`.
* - Always show true `$this` value in magic method `__call()`.
* {@internal This could possibly be covered. Similar logic as "outside object context",
* but with function name check and supportsBelow('7.0').}
*
* PHP version 7.1
*
* @link https://www.php.net/manual/en/migration71.other-changes.php#migration71.other-changes.inconsistency-fixes-to-this
* @link https://wiki.php.net/rfc/this_var
*
* @since 9.1.0
*/
class ForbiddenThisUseContextsSniff extends Sniff
{
/**
* OO scope tokens.
*
* Duplicate of Tokens::$ooScopeTokens array in PHPCS which was added in 3.1.0.
*
* @since 9.1.0
*
* @var array
*/
private $ooScopeTokens = array(
'T_CLASS' => \T_CLASS,
'T_INTERFACE' => \T_INTERFACE,
'T_TRAIT' => \T_TRAIT,
);
/**
* Scopes to skip over when examining the contents of functions.
*
* @since 9.1.0
*
* @var array
*/
private $skipOverScopes = array(
'T_FUNCTION' => true,
'T_CLOSURE' => true,
);
/**
* Valid uses of $this in plain functions or methods outside object context.
*
* @since 9.1.0
*
* @var array
*/
private $validUseOutsideObject = array(
\T_ISSET => true,
\T_EMPTY => true,
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 9.1.0
*
* @return array
*/
public function register()
{
if (\defined('T_ANON_CLASS')) {
$this->ooScopeTokens['T_ANON_CLASS'] = \T_ANON_CLASS;
}
$this->skipOverScopes += $this->ooScopeTokens;
return array(
\T_FUNCTION,
\T_CLOSURE,
\T_GLOBAL,
\T_CATCH,
\T_FOREACH,
\T_UNSET,
);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 9.1.0
*
* @param \PHP_CodeSniffer_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)
{
if ($this->supportsAbove('7.1') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
switch ($tokens[$stackPtr]['code']) {
case \T_FUNCTION:
$this->isThisUsedAsParameter($phpcsFile, $stackPtr);
$this->isThisUsedOutsideObjectContext($phpcsFile, $stackPtr);
break;
case \T_CLOSURE:
$this->isThisUsedAsParameter($phpcsFile, $stackPtr);
break;
case \T_GLOBAL:
/*
* $this can no longer be imported using the `global` keyword.
* This worked in PHP 7.0, though in PHP 5.x, it would throw a
* fatal "Cannot re-assign $this" error.
*/
$endOfStatement = $phpcsFile->findNext(array(\T_SEMICOLON, \T_CLOSE_TAG), ($stackPtr + 1));
if ($endOfStatement === false) {
// No semi-colon - live coding.
return;
}
for ($i = ($stackPtr + 1); $i < $endOfStatement; $i++) {
if ($tokens[$i]['code'] !== \T_VARIABLE || $tokens[$i]['content'] !== '$this') {
continue;
}
$phpcsFile->addError(
'"$this" can no longer be used with the "global" keyword since PHP 7.1.',
$i,
'Global'
);
}
break;
case \T_CATCH:
/*
* $this can no longer be used as a catch variable.
*/
if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
return;
}
$varPtr = $phpcsFile->findNext(
\T_VARIABLE,
($tokens[$stackPtr]['parenthesis_opener'] + 1),
$tokens[$stackPtr]['parenthesis_closer']
);
if ($varPtr === false || $tokens[$varPtr]['content'] !== '$this') {
return;
}
$phpcsFile->addError(
'"$this" can no longer be used as a catch variable since PHP 7.1.',
$varPtr,
'Catch'
);
break;
case \T_FOREACH:
/*
* $this can no longer be used as a foreach *value* variable.
* This worked in PHP 7.0, though in PHP 5.x, it would throw a
* fatal "Cannot re-assign $this" error.
*/
if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
return;
}
$stopPtr = $phpcsFile->findPrevious(
array(\T_AS, \T_DOUBLE_ARROW),
($tokens[$stackPtr]['parenthesis_closer'] - 1),
$tokens[$stackPtr]['parenthesis_opener']
);
if ($stopPtr === false) {
return;
}
$valueVarPtr = $phpcsFile->findNext(
\T_VARIABLE,
($stopPtr + 1),
$tokens[$stackPtr]['parenthesis_closer']
);
if ($valueVarPtr === false || $tokens[$valueVarPtr]['content'] !== '$this') {
return;
}
$afterThis = $phpcsFile->findNext(
Tokens::$emptyTokens,
($valueVarPtr + 1),
$tokens[$stackPtr]['parenthesis_closer'],
true
);
if ($afterThis !== false
&& ($tokens[$afterThis]['code'] === \T_OBJECT_OPERATOR
|| $tokens[$afterThis]['code'] === \T_DOUBLE_COLON)
) {
return;
}
$phpcsFile->addError(
'"$this" can no longer be used as value variable in a foreach control structure since PHP 7.1.',
$valueVarPtr,
'ForeachValueVar'
);
break;
case \T_UNSET:
/*
* $this can no longer be unset.
*/
$openParenthesis = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($openParenthesis === false
|| $tokens[$openParenthesis]['code'] !== \T_OPEN_PARENTHESIS
|| isset($tokens[$openParenthesis]['parenthesis_closer']) === false
) {
return;
}
for ($i = ($openParenthesis + 1); $i < $tokens[$openParenthesis]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== \T_VARIABLE || $tokens[$i]['content'] !== '$this') {
continue;
}
$afterThis = $phpcsFile->findNext(
Tokens::$emptyTokens,
($i + 1),
$tokens[$openParenthesis]['parenthesis_closer'],
true
);
if ($afterThis !== false
&& ($tokens[$afterThis]['code'] === \T_OBJECT_OPERATOR
|| $tokens[$afterThis]['code'] === \T_DOUBLE_COLON
|| $tokens[$afterThis]['code'] === \T_OPEN_SQUARE_BRACKET)
) {
$i = $afterThis;
continue;
}
$phpcsFile->addError(
'"$this" can no longer be unset since PHP 7.1.',
$i,
'Unset'
);
}
break;
}
}
/**
* Check if $this is used as a parameter in a function declaration.
*
* $this can no longer be used as a parameter in a *global* function.
* Use as a parameter in a method was already an error prior to PHP 7.1.
*
* @since 9.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
protected function isThisUsedAsParameter(File $phpcsFile, $stackPtr)
{
if ($this->validDirectScope($phpcsFile, $stackPtr, $this->ooScopeTokens) !== false) {
return;
}
$params = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
if (empty($params)) {
return;
}
$tokens = $phpcsFile->getTokens();
foreach ($params as $param) {
if ($param['name'] !== '$this') {
continue;
}
if ($tokens[$stackPtr]['code'] === \T_FUNCTION) {
$phpcsFile->addError(
'"$this" can no longer be used as a parameter since PHP 7.1.',
$param['token'],
'FunctionParam'
);
} else {
$phpcsFile->addError(
'"$this" can no longer be used as a closure parameter since PHP 7.0.7.',
$param['token'],
'ClosureParam'
);
}
}
}
/**
* Check if $this is used in a plain function or method.
*
* Prior to PHP 7.1, this would result in an "undefined variable" notice
* and execution would continue with $this regarded as `null`.
* As of PHP 7.1, this throws an exception.
*
* Note: use within isset() and empty() to check object context is still allowed.
* Note: $this can still be used within a closure.
*
* @since 9.1.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
protected function isThisUsedOutsideObjectContext(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
return;
}
if ($this->validDirectScope($phpcsFile, $stackPtr, $this->ooScopeTokens) !== false) {
$methodProps = $phpcsFile->getMethodProperties($stackPtr);
if ($methodProps['is_static'] === false) {
return;
} else {
$methodName = $phpcsFile->getDeclarationName($stackPtr);
if ($methodName === '__call') {
/*
* This is an exception.
* @link https://wiki.php.net/rfc/this_var#always_show_true_this_value_in_magic_method_call
*/
return;
}
}
}
for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
if (isset($this->skipOverScopes[$tokens[$i]['type']])) {
if (isset($tokens[$i]['scope_closer']) === false) {
// Live coding or parse error, will only lead to inaccurate results.
return;
}
// Skip over nested structures.
$i = $tokens[$i]['scope_closer'];
continue;
}
if ($tokens[$i]['code'] !== \T_VARIABLE || $tokens[$i]['content'] !== '$this') {
continue;
}
if (isset($tokens[$i]['nested_parenthesis']) === true) {
$nestedParenthesis = $tokens[$i]['nested_parenthesis'];
$nestedOpenParenthesis = array_keys($nestedParenthesis);
$lastOpenParenthesis = array_pop($nestedOpenParenthesis);
$previousNonEmpty = $phpcsFile->findPrevious(
Tokens::$emptyTokens,
($lastOpenParenthesis - 1),
null,
true,
null,
true
);
if (isset($this->validUseOutsideObject[$tokens[$previousNonEmpty]['code']])) {
continue;
}
}
$phpcsFile->addError(
'"$this" can no longer be used in a plain function or method since PHP 7.1.',
$i,
'OutsideObjectContext'
);
}
}
}