Introducing coding standards validation.

This commit is contained in:
Marcos Schratzenstaller
2019-04-22 17:28:46 -03:00
committed by Nathan Rice
parent 6e40c921ab
commit fd504d41ac
1624 changed files with 190339 additions and 262 deletions
@@ -0,0 +1,124 @@
<?php
/**
* \PHPCompatibility\Sniffs\Variables\ForbiddenGlobalVariableVariableSniff.
*
* PHP version 7.0
*
* @category PHP
* @package PHPCompatibility
* @author Wim Godden <wim@cu.be>
*/
namespace PHPCompatibility\Sniffs\Variables;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* \PHPCompatibility\Sniffs\Variables\ForbiddenGlobalVariableVariableSniff.
*
* Variable variables are forbidden with global
*
* PHP version 7.0
*
* @category PHP
* @package PHPCompatibility
* @author Wim Godden <wim@cu.be>
*/
class ForbiddenGlobalVariableVariableSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(T_GLOBAL);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @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.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$endOfStatement = $phpcsFile->findNext(array(T_SEMICOLON, T_CLOSE_TAG), ($stackPtr + 1));
if ($endOfStatement === false) {
// No semi-colon - live coding.
return;
}
for ($ptr = ($stackPtr + 1); $ptr <= $endOfStatement; $ptr++) {
$errorThrown = false;
$nextComma = $phpcsFile->findNext(T_COMMA, $ptr, $endOfStatement, false, null, true);
$varEnd = ($nextComma === false) ? $endOfStatement : $nextComma;
$variable = $phpcsFile->findNext(T_VARIABLE, $ptr, $varEnd);
$varString = trim($phpcsFile->getTokensAsString($ptr, ($varEnd - $ptr)));
$data = array($varString);
if ($variable !== false) {
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($variable - 1), $ptr, true);
if ($prev !== false && $tokens[$prev]['type'] === 'T_DOLLAR') {
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($variable + 1), $varEnd, true);
if ($next !== false
&& in_array($tokens[$next]['code'], array(T_OPEN_SQUARE_BRACKET, T_OBJECT_OPERATOR, T_DOUBLE_COLON), true) === true
) {
$phpcsFile->addError(
'Global with variable variables is not allowed since PHP 7.0. Found %s',
$variable,
'Found',
$data
);
$errorThrown = true;
} else {
$phpcsFile->addWarning(
'Global with anything other than bare variables is discouraged since PHP 7.0. Found %s',
$variable,
'NonBareVariableFound',
$data
);
$errorThrown = true;
}
}
}
if ($errorThrown === false) {
$dollar = $phpcsFile->findNext(T_DOLLAR, $ptr, $varEnd);
if ($dollar !== false) {
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($dollar + 1), $varEnd, true);
if ($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET) {
$phpcsFile->addWarning(
'Global with anything other than bare variables is discouraged since PHP 7.0. Found %s',
$dollar,
'NonBareVariableFound',
$data
);
}
}
}
// Move the stack pointer forward to the next variable for multi-variable statements.
if ($nextComma === false) {
break;
}
$ptr = $nextComma;
}
}
}
@@ -0,0 +1,424 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2018 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://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'
);
}
}
}
@@ -0,0 +1,111 @@
<?php
/**
* \PHPCompatibility\Sniffs\Variables\NewUniformVariableSyntax.
*
* PHP version 7.0
*
* @category PHP
* @package PHPCompatibility
* @author Juliette Reinders Folmer <phpcompatibility_nospam@adviesenzo.nl>
*/
namespace PHPCompatibility\Sniffs\Variables;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* \PHPCompatibility\Sniffs\Variables\NewUniformVariableSyntax.
*
* The interpretation of variable variables has changed in PHP 7.0.
*
* PHP version 7.0
*
* @category PHP
* @package PHPCompatibility
* @author Juliette Reinders Folmer <phpcompatibility_nospam@adviesenzo.nl>
*/
class NewUniformVariableSyntaxSniff extends Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(T_VARIABLE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @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.0') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
// Verify that the next token is a square open bracket. If not, bow out.
$nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
if ($nextToken === false || $tokens[$nextToken]['code'] !== T_OPEN_SQUARE_BRACKET || isset($tokens[$nextToken]['bracket_closer']) === false) {
return;
}
// The previous non-empty token has to be a $, -> or ::.
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
if ($prevToken === false || in_array($tokens[$prevToken]['code'], array(T_DOLLAR, T_OBJECT_OPERATOR, T_DOUBLE_COLON), true) === false) {
return;
}
// For static object calls, it only applies when this is a function call.
if ($tokens[$prevToken]['code'] === T_DOUBLE_COLON) {
$hasBrackets = $tokens[$nextToken]['bracket_closer'];
while (($hasBrackets = $phpcsFile->findNext(Tokens::$emptyTokens, ($hasBrackets + 1), null, true, null, true)) !== false) {
if ($tokens[$hasBrackets]['code'] === T_OPEN_SQUARE_BRACKET) {
if (isset($tokens[$hasBrackets]['bracket_closer'])) {
$hasBrackets = $tokens[$hasBrackets]['bracket_closer'];
continue;
} else {
// Live coding.
return;
}
} elseif ($tokens[$hasBrackets]['code'] === T_OPEN_PARENTHESIS) {
// Caught!
break;
} else {
// Not a function call, so bow out.
return;
}
}
// Now let's also prevent false positives when used with self and static which still work fine.
$classToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevToken - 1), null, true, null, true);
if ($classToken !== false) {
if ($tokens[$classToken]['code'] === T_STATIC || $tokens[$classToken]['code'] === T_SELF) {
return;
} elseif ($tokens[$classToken]['code'] === T_STRING && $tokens[$classToken]['content'] === 'self') {
return;
}
}
}
$phpcsFile->addError(
'Indirect access to variables, properties and methods will be evaluated strictly in left-to-right order since PHP 7.0. Use curly braces to remove ambiguity.',
$stackPtr,
'Found'
);
}
}
@@ -0,0 +1,293 @@
<?php
/**
* \PHPCompatibility\Sniffs\Variables\RemovedPredefinedGlobalVariablesSniff.
*
* @category PHP
* @package PHPCompatibility
* @author Wim Godden <wim.godden@cu.be>
*/
namespace PHPCompatibility\Sniffs\Variables;
use PHPCompatibility\AbstractRemovedFeatureSniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer_Tokens as Tokens;
/**
* \PHPCompatibility\Sniffs\Variables\RemovedPredefinedGlobalVariablesSniff.
*
* Discourages the use of removed global variables. Suggests alternative extensions if available
*
* @category PHP
* @package PHPCompatibility
* @author Wim Godden <wim.godden@cu.be>
*/
class RemovedPredefinedGlobalVariablesSniff extends AbstractRemovedFeatureSniff
{
/**
* A list of removed global variables with their alternative, if any.
*
* The array lists : version number with false (deprecated) and true (removed).
* If's sufficient to list the first version where the variable was deprecated/removed.
*
* @var array(string|null)
*/
protected $removedGlobalVariables = array(
'HTTP_POST_VARS' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_POST',
),
'HTTP_GET_VARS' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_GET',
),
'HTTP_ENV_VARS' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_ENV',
),
'HTTP_SERVER_VARS' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_SERVER',
),
'HTTP_COOKIE_VARS' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_COOKIE',
),
'HTTP_SESSION_VARS' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_SESSION',
),
'HTTP_POST_FILES' => array(
'5.3' => false,
'5.4' => true,
'alternative' => '$_FILES',
),
'HTTP_RAW_POST_DATA' => array(
'5.6' => false,
'7.0' => true,
'alternative' => 'php://input',
),
'php_errormsg' => array(
'7.2' => false,
'alternative' => 'error_get_last()',
),
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(T_VARIABLE);
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @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('5.3') === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$varName = substr($tokens[$stackPtr]['content'], 1);
if (isset($this->removedGlobalVariables[$varName]) === false) {
return;
}
if ($this->isClassProperty($phpcsFile, $stackPtr) === true) {
// Ok, so this was a class property declaration, not our concern.
return;
}
// Check for static usage of class properties shadowing the removed global variables.
if ($this->inClassScope($phpcsFile, $stackPtr, false) === true) {
$prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
if ($prevToken !== false && $tokens[$prevToken]['code'] === T_DOUBLE_COLON) {
return;
}
}
// Do some additional checks for the $php_errormsg variable.
if ($varName === 'php_errormsg'
&& $this->isTargetPHPErrormsgVar($phpcsFile, $stackPtr, $tokens) === false
) {
return;
}
// Still here, so throw an error/warning.
$itemInfo = array(
'name' => $varName,
);
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Get the relevant sub-array for a specific item from a multi-dimensional array.
*
* @param array $itemInfo Base information about the item.
*
* @return array Version and other information about the item.
*/
public function getItemArray(array $itemInfo)
{
return $this->removedGlobalVariables[$itemInfo['name']];
}
/**
* Get the error message template for this sniff.
*
* @return string
*/
protected function getErrorMsgTemplate()
{
return "Global variable '\$%s' is ";
}
/**
* Filter the error message before it's passed to PHPCS.
*
* @param string $error The error message which was created.
* @param array $itemInfo Base information about the item this error message applies to.
* @param array $errorInfo Detail information about an item this error message applies to.
*
* @return string
*/
protected function filterErrorMsg($error, array $itemInfo, array $errorInfo)
{
if ($itemInfo['name'] === 'php_errormsg') {
$error = str_replace('Global', 'The', $error);
}
return $error;
}
/**
* Run some additional checks for the `$php_errormsg` variable.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
* @param array $tokens Token array of the current file.
*
* @return bool
*/
private function isTargetPHPErrormsgVar(File $phpcsFile, $stackPtr, array $tokens)
{
$scopeStart = 0;
/*
* If the variable is detected within the scope of a function/closure, limit the checking.
*/
$function = $phpcsFile->getCondition($stackPtr, T_CLOSURE);
if ($function === false) {
$function = $phpcsFile->getCondition($stackPtr, T_FUNCTION);
}
// It could also be a function param, which is not in the function scope.
if ($function === false && isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
$nestedParentheses = $tokens[$stackPtr]['nested_parenthesis'];
$parenthesisCloser = end($nestedParentheses);
if (isset($tokens[$parenthesisCloser]['parenthesis_owner'])
&& ($tokens[$tokens[$parenthesisCloser]['parenthesis_owner']]['code'] === T_FUNCTION
|| $tokens[$tokens[$parenthesisCloser]['parenthesis_owner']]['code'] === T_CLOSURE)
) {
$function = $tokens[$parenthesisCloser]['parenthesis_owner'];
}
}
if ($function !== false) {
$scopeStart = $tokens[$function]['scope_opener'];
}
/*
* Now, let's do some additional checks.
*/
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
// Is the variable being used as an array ?
if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_OPEN_SQUARE_BRACKET) {
// The PHP native variable is a string, so this is probably not it
// (except for array access to string, but why would you in this case ?).
return false;
}
// Is this a variable assignment ?
if ($nextNonEmpty !== false
&& isset(Tokens::$assignmentTokens[$tokens[$nextNonEmpty]['code']]) === true
) {
return false;
}
// Is this a function param shadowing the PHP native one ?
if ($function !== false) {
$parameters = PHPCSHelper::getMethodParameters($phpcsFile, $function);
if (is_array($parameters) === true && empty($parameters) === false) {
foreach ($parameters as $param) {
if ($param['name'] === '$php_errormsg') {
return false;
}
}
}
}
$skipPast = array(
'T_CLASS' => true,
'T_ANON_CLASS' => true,
'T_INTERFACE' => true,
'T_TRAIT' => true,
'T_FUNCTION' => true,
'T_CLOSURE' => true,
);
// Walk back and see if there is an assignment to the variable within the same scope.
for ($i = ($stackPtr - 1); $i >= $scopeStart; $i--) {
if ($tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
&& isset($tokens[$i]['scope_condition'])
&& isset($skipPast[$tokens[$tokens[$i]['scope_condition']]['type']])
) {
// Skip past functions, classes etc.
$i = $tokens[$i]['scope_condition'];
continue;
}
if ($tokens[$i]['code'] !== T_VARIABLE || $tokens[$i]['content'] !== '$php_errormsg') {
continue;
}
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
if ($nextNonEmpty !== false
&& isset(Tokens::$assignmentTokens[$tokens[$nextNonEmpty]['code']]) === true
) {
return false;
}
}
return true;
}
}