clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Variables;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use function in_array;
use const T_VARIABLE;
class DisallowSuperGlobalVariableSniff implements Sniff
{
public const CODE_DISALLOWED_SUPER_GLOBAL_VARIABLE = 'DisallowedSuperGlobalVariable';
private const SUPER_GLOBALS = [
'$GLOBALS',
'$_SERVER',
'$_GET',
'$_POST',
'$_FILES',
'$_COOKIE',
'$_SESSION',
'$_REQUEST',
'$_ENV',
];
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_VARIABLE,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $variablePointer
*/
public function process(File $phpcsFile, $variablePointer): void
{
$tokens = $phpcsFile->getTokens();
if (!in_array($tokens[$variablePointer]['content'], self::SUPER_GLOBALS, true)) {
return;
}
$phpcsFile->addError('Use of super global variable is disallowed.', $variablePointer, self::CODE_DISALLOWED_SUPER_GLOBAL_VARIABLE);
}
}

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Variables;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use const T_DOLLAR;
use const T_DOLLAR_OPEN_CURLY_BRACES;
class DisallowVariableVariableSniff implements Sniff
{
public const CODE_DISALLOWED_VARIABLE_VARIABLE = 'DisallowedVariableVariable';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_DOLLAR,
T_DOLLAR_OPEN_CURLY_BRACES,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $pointer
*/
public function process(File $phpcsFile, $pointer): void
{
$phpcsFile->addError('Use of variable variable is disallowed.', $pointer, self::CODE_DISALLOWED_VARIABLE_VARIABLE);
}
}

View File

@@ -0,0 +1,70 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Variables;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function in_array;
use function sprintf;
use const T_DOUBLE_COLON;
use const T_EQUAL;
use const T_OBJECT_OPERATOR;
use const T_VARIABLE;
class DuplicateAssignmentToVariableSniff implements Sniff
{
public const CODE_DUPLICATE_ASSIGNMENT = 'DuplicateAssignment';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_EQUAL,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $assignmentPointer
*/
public function process(File $phpcsFile, $assignmentPointer): void
{
$tokens = $phpcsFile->getTokens();
$variablePointer = TokenHelper::findPreviousEffective($phpcsFile, $assignmentPointer - 1);
if ($tokens[$variablePointer]['code'] !== T_VARIABLE) {
return;
}
$pointerBeforeVariable = TokenHelper::findPreviousEffective($phpcsFile, $variablePointer - 1);
if (in_array($tokens[$pointerBeforeVariable]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
return;
}
/** @var int $secondVariablePointer */
$secondVariablePointer = TokenHelper::findNextEffective($phpcsFile, $assignmentPointer + 1);
if ($tokens[$secondVariablePointer]['code'] !== T_VARIABLE) {
return;
}
if ($tokens[$variablePointer]['content'] !== $tokens[$secondVariablePointer]['content']) {
return;
}
$pointerAfterSecondVariable = TokenHelper::findNextEffective($phpcsFile, $secondVariablePointer + 1);
if ($tokens[$pointerAfterSecondVariable]['code'] !== T_EQUAL) {
return;
}
$phpcsFile->addError(
sprintf('Duplicate assignment to variable %s.', $tokens[$secondVariablePointer]['content']),
$secondVariablePointer,
self::CODE_DUPLICATE_ASSIGNMENT
);
}
}

View File

@@ -0,0 +1,709 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Variables;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use SlevomatCodingStandard\Helpers\ParameterHelper;
use SlevomatCodingStandard\Helpers\PropertyHelper;
use SlevomatCodingStandard\Helpers\ScopeHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use SlevomatCodingStandard\Helpers\VariableHelper;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function array_reverse;
use function in_array;
use function sprintf;
use const T_AND_EQUAL;
use const T_AS;
use const T_BITWISE_AND;
use const T_CLOSE_CURLY_BRACKET;
use const T_CLOSE_SHORT_ARRAY;
use const T_CLOSURE;
use const T_COMMA;
use const T_CONCAT_EQUAL;
use const T_DEC;
use const T_DIV_EQUAL;
use const T_DO;
use const T_DOUBLE_ARROW;
use const T_DOUBLE_COLON;
use const T_DOUBLE_QUOTED_STRING;
use const T_ECHO;
use const T_ELSEIF;
use const T_EMPTY;
use const T_EQUAL;
use const T_EVAL;
use const T_EXIT;
use const T_FOR;
use const T_FOREACH;
use const T_GLOBAL;
use const T_HEREDOC;
use const T_IF;
use const T_INC;
use const T_LIST;
use const T_MINUS_EQUAL;
use const T_MOD_EQUAL;
use const T_MUL_EQUAL;
use const T_OBJECT_OPERATOR;
use const T_OPEN_PARENTHESIS;
use const T_OPEN_SHORT_ARRAY;
use const T_OPEN_SQUARE_BRACKET;
use const T_OPEN_TAG;
use const T_OR_EQUAL;
use const T_PLUS_EQUAL;
use const T_POW_EQUAL;
use const T_PRINT;
use const T_RETURN;
use const T_SL_EQUAL;
use const T_SR_EQUAL;
use const T_STATIC;
use const T_STRING;
use const T_STRING_CONCAT;
use const T_USE;
use const T_VARIABLE;
use const T_WHILE;
use const T_XOR_EQUAL;
use const T_YIELD;
class UnusedVariableSniff implements Sniff
{
public const CODE_UNUSED_VARIABLE = 'UnusedVariable';
/** @var bool */
public $ignoreUnusedValuesWhenOnlyKeysAreUsedInForeach = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_VARIABLE,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $variablePointer
*/
public function process(File $phpcsFile, $variablePointer): void
{
if (!$this->isAssignment($phpcsFile, $variablePointer)) {
return;
}
$tokens = $phpcsFile->getTokens();
$variableName = $tokens[$variablePointer]['content'];
if (in_array($variableName, [
'$this',
'$GLOBALS',
'$_SERVER',
'$_GET',
'$_POST',
'$_FILES',
'$_COOKIE',
'$_SESSION',
'$_REQUEST',
'$_ENV',
], true)) {
return;
}
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $variablePointer - 1);
if (in_array($tokens[$previousPointer]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
// Property
return;
}
if (in_array($tokens[$previousPointer]['code'], Tokens::$castTokens, true)) {
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
}
if (in_array($tokens[$previousPointer]['code'], [
T_EQUAL,
T_PLUS_EQUAL,
T_MINUS_EQUAL,
T_MUL_EQUAL,
T_DIV_EQUAL,
T_POW_EQUAL,
T_MOD_EQUAL,
T_AND_EQUAL,
T_OR_EQUAL,
T_XOR_EQUAL,
T_SL_EQUAL,
T_SR_EQUAL,
T_CONCAT_EQUAL,
T_YIELD,
], true)) {
return;
}
if ($this->isUsedAsParameter($phpcsFile, $variablePointer)) {
return;
}
if ($this->isUsedInForLoopCondition($phpcsFile, $variablePointer, $variableName)) {
return;
}
if ($this->isDefinedInDoConditionAndUsedInLoop($phpcsFile, $variablePointer, $variableName)) {
return;
}
if ($this->isUsedInLoopCycle($phpcsFile, $variablePointer, $variableName)) {
return;
}
if ($this->isUsedAsKeyOrValueInArray($phpcsFile, $variablePointer)) {
return;
}
if ($this->isValueInForeachAndErrorIsIgnored($phpcsFile, $variablePointer)) {
return;
}
$scopeOwnerPointer = ScopeHelper::getRootPointer($phpcsFile, $variablePointer - 1);
foreach (array_reverse($tokens[$variablePointer]['conditions'], true) as $conditionPointer => $conditionTokenCode) {
if (in_array($conditionTokenCode, TokenHelper::$functionTokenCodes, true)) {
$scopeOwnerPointer = $conditionPointer;
break;
}
}
if (in_array($tokens[$scopeOwnerPointer]['code'], TokenHelper::$functionTokenCodes, true)) {
if ($this->isStaticOrGlobalVariable($phpcsFile, $scopeOwnerPointer, $variableName)) {
return;
}
if ($this->isParameterPassedByReference($phpcsFile, $scopeOwnerPointer, $variableName)) {
return;
}
if (
$tokens[$scopeOwnerPointer]['code'] === T_CLOSURE
&& $this->isInheritedVariablePassedByReference($phpcsFile, $scopeOwnerPointer, $variableName)
) {
return;
}
}
if ($this->isReference($phpcsFile, $scopeOwnerPointer, $variablePointer)) {
return;
}
if (VariableHelper::isUsedInScopeAfterPointer($phpcsFile, $scopeOwnerPointer, $variablePointer, $variablePointer + 1)) {
return;
}
if ($this->isPartOfStatementAndWithIncrementOrDecrementOperator($phpcsFile, $variablePointer)) {
return;
}
$phpcsFile->addError(
sprintf('Unused variable %s.', $variableName),
$variablePointer,
self::CODE_UNUSED_VARIABLE
);
}
private function isAssignment(File $phpcsFile, int $variablePointer): bool
{
$tokens = $phpcsFile->getTokens();
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $variablePointer + 1);
if (in_array($tokens[$nextPointer]['code'], [
T_EQUAL,
T_PLUS_EQUAL,
T_MINUS_EQUAL,
T_MUL_EQUAL,
T_DIV_EQUAL,
T_POW_EQUAL,
T_MOD_EQUAL,
T_AND_EQUAL,
T_OR_EQUAL,
T_XOR_EQUAL,
T_SL_EQUAL,
T_SR_EQUAL,
T_CONCAT_EQUAL,
], true)) {
if ($tokens[$nextPointer]['code'] === T_EQUAL) {
if (PropertyHelper::isProperty($phpcsFile, $variablePointer)) {
return false;
}
if (ParameterHelper::isParameter($phpcsFile, $variablePointer)) {
return false;
}
}
return true;
}
$actualPointer = $variablePointer;
do {
$parenthesisOpenerPointer = $this->findOpenerOfNestedParentheses($phpcsFile, $actualPointer);
$parenthesisOwnerPointer = $this->findOwnerOfNestedParentheses($phpcsFile, $actualPointer);
$actualPointer = $parenthesisOpenerPointer;
} while ($parenthesisOwnerPointer === null && isset($tokens[$actualPointer]['nested_parenthesis']));
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $variablePointer - 1);
if (
in_array($tokens[$nextPointer]['code'], [T_INC, T_DEC], true)
|| in_array($tokens[$previousPointer]['code'], [T_INC, T_DEC], true)
) {
if ($parenthesisOwnerPointer === null) {
return true;
}
return !in_array($tokens[$parenthesisOwnerPointer]['code'], [T_FOR, T_WHILE, T_IF, T_ELSEIF], true);
}
if ($parenthesisOwnerPointer !== null && $tokens[$parenthesisOwnerPointer]['code'] === T_FOREACH) {
$pointerBeforeVariable = TokenHelper::findPreviousEffective($phpcsFile, $variablePointer - 1);
return in_array($tokens[$pointerBeforeVariable]['code'], [T_AS, T_DOUBLE_ARROW], true);
}
if ($parenthesisOpenerPointer !== null) {
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if ($tokens[$pointerBeforeParenthesisOpener]['code'] === T_LIST) {
return true;
}
}
$possibleShortListCloserPointer = TokenHelper::findNextExcluding(
$phpcsFile,
array_merge(TokenHelper::$ineffectiveTokenCodes, [T_VARIABLE, T_COMMA]),
$variablePointer + 1
);
if ($tokens[$possibleShortListCloserPointer]['code'] === T_CLOSE_SHORT_ARRAY) {
return $tokens[TokenHelper::findNextEffective($phpcsFile, $possibleShortListCloserPointer + 1)]['code'] === T_EQUAL;
}
return false;
}
private function isUsedAsParameter(File $phpcsFile, int $variablePointer): bool
{
$parenthesisOpenerPointer = $this->findOpenerOfNestedParentheses($phpcsFile, $variablePointer);
if ($parenthesisOpenerPointer === null) {
return false;
}
if (!ScopeHelper::isInSameScope($phpcsFile, $parenthesisOpenerPointer, $variablePointer)) {
return false;
}
return $phpcsFile->getTokens()[TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1)]['code'] === T_STRING;
}
private function isUsedInForLoopCondition(File $phpcsFile, int $variablePointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = $this->findOpenerOfNestedParentheses($phpcsFile, $variablePointer);
if ($parenthesisOpenerPointer === null) {
return false;
}
$parenthesisOwnerPointer = $this->findOwnerOfNestedParentheses($phpcsFile, $variablePointer);
if ($parenthesisOwnerPointer === null) {
return false;
}
if ($tokens[$parenthesisOwnerPointer]['code'] !== T_FOR) {
return false;
}
for ($i = $parenthesisOpenerPointer + 1; $i < $tokens[$parenthesisOwnerPointer]['parenthesis_closer']; $i++) {
if ($i === $variablePointer) {
continue;
}
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
return true;
}
return false;
}
private function isDefinedInDoConditionAndUsedInLoop(File $phpcsFile, int $variablePointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
$parenthesisOpener = TokenHelper::findPrevious($phpcsFile, T_OPEN_PARENTHESIS, $variablePointer - 1);
if ($parenthesisOpener === null || $tokens[$parenthesisOpener]['parenthesis_closer'] < $variablePointer) {
return false;
}
$whilePointer = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpener - 1);
if ($tokens[$whilePointer]['code'] !== T_WHILE) {
return false;
}
$loopCloserPointer = TokenHelper::findPreviousEffective($phpcsFile, $whilePointer - 1);
if ($tokens[$loopCloserPointer]['code'] !== T_CLOSE_CURLY_BRACKET) {
return false;
}
$doPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$loopCloserPointer]['bracket_opener'] - 1);
if ($tokens[$doPointer]['code'] !== T_DO) {
return false;
}
return TokenHelper::findNextContent(
$phpcsFile,
T_VARIABLE,
$variableName,
$tokens[$loopCloserPointer]['bracket_opener'] + 1,
$loopCloserPointer
) !== null;
}
private function isUsedInLoopCycle(File $phpcsFile, int $variablePointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
$loopPointer = null;
foreach (array_reverse($tokens[$variablePointer]['conditions'], true) as $conditionPointer => $conditionTokenCode) {
if (in_array($conditionTokenCode, TokenHelper::$functionTokenCodes, true)) {
break;
}
if (!in_array($conditionTokenCode, [T_FOREACH, T_FOR, T_DO, T_WHILE], true)) {
continue;
}
$loopPointer = $conditionPointer;
$loopConditionPointer = $conditionTokenCode === T_DO
? TokenHelper::findNextEffective($phpcsFile, $tokens[$loopPointer]['scope_closer'] + 1)
: $loopPointer;
$variableUsedInLoopConditionPointer = TokenHelper::findNextContent(
$phpcsFile,
T_VARIABLE,
$variableName,
$tokens[$loopConditionPointer]['parenthesis_opener'] + 1,
$tokens[$loopConditionPointer]['parenthesis_closer']
);
if (
$variableUsedInLoopConditionPointer === null
|| $variableUsedInLoopConditionPointer === $variablePointer
) {
continue;
}
if ($conditionTokenCode !== T_FOREACH) {
return true;
}
$pointerBeforeVariableUsedInLoopCondition = TokenHelper::findPreviousEffective(
$phpcsFile,
$variableUsedInLoopConditionPointer - 1
);
if ($tokens[$pointerBeforeVariableUsedInLoopCondition]['code'] === T_BITWISE_AND) {
return true;
}
}
if ($loopPointer === null) {
return false;
}
for ($i = $tokens[$loopPointer]['scope_opener'] + 1; $i < $tokens[$loopPointer]['scope_closer']; $i++) {
if (
in_array($tokens[$i]['code'], [T_DOUBLE_QUOTED_STRING, T_HEREDOC], true)
&& VariableHelper::isUsedInScopeInString($phpcsFile, $variableName, $i)
) {
return true;
}
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
if (!$this->isAssignment($phpcsFile, $i)) {
return true;
}
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $i + 1);
if (!in_array($tokens[$nextPointer]['code'], [
T_INC,
T_DEC,
T_PLUS_EQUAL,
T_MINUS_EQUAL,
T_MUL_EQUAL,
T_DIV_EQUAL,
T_POW_EQUAL,
T_MOD_EQUAL,
T_AND_EQUAL,
T_OR_EQUAL,
T_XOR_EQUAL,
T_SL_EQUAL,
T_SR_EQUAL,
T_CONCAT_EQUAL,
], true)) {
continue;
}
$parenthesisOwnerPointer = $this->findNestedParenthesisWithOwner($phpcsFile, $i);
if (
$parenthesisOwnerPointer !== null
&& in_array($tokens[$parenthesisOwnerPointer]['code'], [T_IF, T_ELSEIF], true)
) {
return true;
}
}
return false;
}
private function isUsedAsKeyOrValueInArray(File $phpcsFile, int $variablePointer): bool
{
$tokens = $phpcsFile->getTokens();
$squareBracketOpenerPointer = TokenHelper::findPrevious($phpcsFile, T_OPEN_SQUARE_BRACKET, $variablePointer - 1);
if (
$squareBracketOpenerPointer !== null
&& $tokens[$squareBracketOpenerPointer]['bracket_closer'] > $variablePointer
) {
return true;
}
$arrayOpenerPointer = TokenHelper::findPrevious($phpcsFile, T_OPEN_SHORT_ARRAY, $variablePointer - 1);
if ($arrayOpenerPointer === null) {
return false;
}
$arrayCloserPointer = $tokens[$arrayOpenerPointer]['bracket_closer'];
if ($arrayCloserPointer < $variablePointer) {
return false;
}
$pointerAfterArrayCloser = TokenHelper::findNextEffective($phpcsFile, $arrayCloserPointer + 1);
if ($tokens[$pointerAfterArrayCloser]['code'] === T_EQUAL) {
return false;
}
$pointerBeforeVariable = TokenHelper::findPreviousEffective($phpcsFile, $variablePointer - 1);
if (in_array($tokens[$pointerBeforeVariable]['code'], [T_INC, T_DEC], true)) {
$pointerBeforeVariable = TokenHelper::findPreviousEffective($phpcsFile, $pointerBeforeVariable - 1);
}
return in_array($tokens[$pointerBeforeVariable]['code'], [T_OPEN_SHORT_ARRAY, T_COMMA, T_DOUBLE_ARROW], true);
}
private function isValueInForeachAndErrorIsIgnored(File $phpcsFile, int $variablePointer): bool
{
$tokens = $phpcsFile->getTokens();
$parenthesisOwnerPointer = $this->findNestedParenthesisWithOwner($phpcsFile, $variablePointer);
$isInForeach = $parenthesisOwnerPointer !== null && $tokens[$parenthesisOwnerPointer]['code'] === T_FOREACH;
if (!$isInForeach) {
return false;
}
$pointerAfterVariable = TokenHelper::findNextEffective($phpcsFile, $variablePointer + 1);
if ($pointerAfterVariable !== null && $tokens[$pointerAfterVariable]['code'] === T_DOUBLE_ARROW) {
return false;
}
return $this->ignoreUnusedValuesWhenOnlyKeysAreUsedInForeach;
}
private function isStaticOrGlobalVariable(File $phpcsFile, int $functionPointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
for ($i = $tokens[$functionPointer]['scope_opener'] + 1; $i < $tokens[$functionPointer]['scope_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
$pointerBeforeParameter = TokenHelper::findPreviousEffective($phpcsFile, $i - 1);
if (in_array($tokens[$pointerBeforeParameter]['code'], [T_STATIC, T_GLOBAL], true)) {
return true;
}
}
return false;
}
private function isParameterPassedByReference(File $phpcsFile, int $functionPointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
$pointerBeforeParameter = TokenHelper::findPreviousEffective($phpcsFile, $i - 1);
if ($tokens[$pointerBeforeParameter]['code'] === T_BITWISE_AND) {
return true;
}
}
return false;
}
private function isInheritedVariablePassedByReference(File $phpcsFile, int $functionPointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
$usePointer = TokenHelper::findNextEffective($phpcsFile, $tokens[$functionPointer]['parenthesis_closer'] + 1);
if ($tokens[$usePointer]['code'] !== T_USE) {
return false;
}
$useParenthesisOpener = TokenHelper::findNextEffective($phpcsFile, $usePointer + 1);
for ($i = $useParenthesisOpener + 1; $i < $tokens[$useParenthesisOpener]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
$pointerBeforeInheritedVariable = TokenHelper::findPreviousEffective($phpcsFile, $i - 1);
if ($tokens[$pointerBeforeInheritedVariable]['code'] === T_BITWISE_AND) {
return true;
}
}
return false;
}
private function isReference(File $phpcsFile, int $scopeOwnerPointer, int $variablePointer): bool
{
$tokens = $phpcsFile->getTokens();
$scopeOpenerPointer = $tokens[$scopeOwnerPointer]['code'] === T_OPEN_TAG
? $scopeOwnerPointer
: $tokens[$scopeOwnerPointer]['scope_opener'];
for ($i = $scopeOpenerPointer + 1; $i < $variablePointer; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $tokens[$variablePointer]['content']) {
continue;
}
$assignmentPointer = TokenHelper::findNextEffective($phpcsFile, $i + 1);
if ($tokens[$assignmentPointer]['code'] !== T_EQUAL) {
continue;
}
$referencePointer = TokenHelper::findNextEffective($phpcsFile, $assignmentPointer + 1);
if ($tokens[$referencePointer]['code'] === T_BITWISE_AND) {
return true;
}
}
return false;
}
private function isPartOfStatementAndWithIncrementOrDecrementOperator(File $phpcsFile, int $variablePointer): bool
{
$tokens = $phpcsFile->getTokens();
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $variablePointer - 1);
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $variablePointer + 1);
if (in_array($tokens[$previousPointer]['code'], [T_DEC, T_INC], true)) {
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
} elseif ($nextPointer !== null && in_array($tokens[$nextPointer]['code'], [T_DEC, T_INC], true)) {
// Nothing
} else {
return false;
}
if ($tokens[$previousPointer]['code'] === T_OPEN_PARENTHESIS) {
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
}
return in_array(
$tokens[$previousPointer]['code'],
array_merge(
[T_STRING_CONCAT, T_ECHO, T_RETURN, T_EXIT, T_PRINT, T_COMMA, T_EMPTY, T_EVAL, T_YIELD],
Tokens::$operators,
Tokens::$assignmentTokens,
Tokens::$booleanOperators,
Tokens::$castTokens
),
true
);
}
private function findNestedParenthesisWithOwner(File $phpcsFile, int $pointer): ?int
{
$tokens = $phpcsFile->getTokens();
if (!array_key_exists('nested_parenthesis', $tokens[$pointer])) {
return null;
}
foreach (array_reverse(array_keys($tokens[$pointer]['nested_parenthesis'])) as $nestedParenthesisOpener) {
if (array_key_exists('parenthesis_owner', $tokens[$nestedParenthesisOpener])) {
return $tokens[$nestedParenthesisOpener]['parenthesis_owner'];
}
}
return null;
}
private function findOpenerOfNestedParentheses(File $phpcsFile, int $pointer): ?int
{
$tokens = $phpcsFile->getTokens();
if (!array_key_exists('nested_parenthesis', $tokens[$pointer])) {
return null;
}
return array_reverse(array_keys($tokens[$pointer]['nested_parenthesis']))[0];
}
private function findOwnerOfNestedParentheses(File $phpcsFile, int $pointer): ?int
{
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = $this->findOpenerOfNestedParentheses($phpcsFile, $pointer);
if ($parenthesisOpenerPointer === null) {
return null;
}
return array_key_exists('parenthesis_owner', $tokens[$parenthesisOpenerPointer])
? $tokens[$parenthesisOpenerPointer]['parenthesis_owner']
: null;
}
}

View File

@@ -0,0 +1,417 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Variables;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\ScopeHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_keys;
use function array_reverse;
use function count;
use function in_array;
use function preg_match;
use function preg_quote;
use function sprintf;
use const T_AND_EQUAL;
use const T_BITWISE_AND;
use const T_CLOSE_CURLY_BRACKET;
use const T_CONCAT_EQUAL;
use const T_DIV_EQUAL;
use const T_DO;
use const T_DOC_COMMENT_CLOSE_TAG;
use const T_DOUBLE_COLON;
use const T_ELSEIF;
use const T_EQUAL;
use const T_FOR;
use const T_FOREACH;
use const T_IF;
use const T_MINUS_EQUAL;
use const T_MOD_EQUAL;
use const T_MUL_EQUAL;
use const T_OPEN_CURLY_BRACKET;
use const T_OPEN_PARENTHESIS;
use const T_OR_EQUAL;
use const T_PLUS_EQUAL;
use const T_POW_EQUAL;
use const T_RETURN;
use const T_SEMICOLON;
use const T_SL_EQUAL;
use const T_SR_EQUAL;
use const T_STATIC;
use const T_STRING;
use const T_SWITCH;
use const T_VARIABLE;
use const T_WHILE;
use const T_XOR_EQUAL;
class UselessVariableSniff implements Sniff
{
public const CODE_USELESS_VARIABLE = 'UselessVariable';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_RETURN,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $returnPointer
*/
public function process(File $phpcsFile, $returnPointer): void
{
$tokens = $phpcsFile->getTokens();
/** @var int $variablePointer */
$variablePointer = TokenHelper::findNextEffective($phpcsFile, $returnPointer + 1);
if ($tokens[$variablePointer]['code'] !== T_VARIABLE) {
return;
}
$returnSemicolonPointer = TokenHelper::findNextEffective($phpcsFile, $variablePointer + 1);
if ($tokens[$returnSemicolonPointer]['code'] !== T_SEMICOLON) {
return;
}
$variableName = $tokens[$variablePointer]['content'];
$functionPointer = $this->findFunctionPointer($phpcsFile, $variablePointer);
if ($functionPointer !== null) {
if ($this->isReturnedByReference($phpcsFile, $functionPointer)) {
return;
}
if ($this->isStaticVariable($phpcsFile, $functionPointer, $variablePointer, $variableName)) {
return;
}
if ($this->isFunctionParameter($phpcsFile, $functionPointer, $variableName)) {
return;
}
}
$previousVariablePointer = $this->findPreviousVariablePointer($phpcsFile, $returnPointer, $variableName);
if ($previousVariablePointer === null) {
return;
}
if (!$this->isAssignmentToVariable($phpcsFile, $previousVariablePointer)) {
return;
}
if ($this->isAssignedInControlStructure($phpcsFile, $previousVariablePointer)) {
return;
}
if ($this->isAssignedInFunctionCall($phpcsFile, $previousVariablePointer)) {
return;
}
if ($this->hasVariableVarAnnotation($phpcsFile, $previousVariablePointer)) {
return;
}
if ($this->hasAnotherAssignmentBefore($phpcsFile, $previousVariablePointer, $variableName)) {
return;
}
if (!$this->areBothPointersNearby($phpcsFile, $previousVariablePointer, $returnPointer)) {
return;
}
$errorParameters = [
sprintf('Useless variable %s.', $variableName),
$previousVariablePointer,
self::CODE_USELESS_VARIABLE,
];
$pointerBeforePreviousVariable = TokenHelper::findPreviousEffective($phpcsFile, $previousVariablePointer - 1);
if (
!in_array($tokens[$pointerBeforePreviousVariable]['code'], [T_SEMICOLON, T_OPEN_CURLY_BRACKET, T_CLOSE_CURLY_BRACKET], true)
&& TokenHelper::findNextEffective($phpcsFile, $returnSemicolonPointer + 1) !== null
) {
$phpcsFile->addError(...$errorParameters);
return;
}
$fix = $phpcsFile->addFixableError(...$errorParameters);
if (!$fix) {
return;
}
/** @var int $assignmentPointer */
$assignmentPointer = TokenHelper::findNextEffective($phpcsFile, $previousVariablePointer + 1);
$assignmentFixerMapping = [
T_PLUS_EQUAL => '+',
T_MINUS_EQUAL => '-',
T_MUL_EQUAL => '*',
T_DIV_EQUAL => '/',
T_POW_EQUAL => '**',
T_MOD_EQUAL => '%',
T_AND_EQUAL => '&',
T_OR_EQUAL => '|',
T_XOR_EQUAL => '^',
T_SL_EQUAL => '<<',
T_SR_EQUAL => '>>',
T_CONCAT_EQUAL => '.',
];
$previousVariableSemicolonPointer = $this->findSemicolon($phpcsFile, $previousVariablePointer);
$phpcsFile->fixer->beginChangeset();
if ($tokens[$assignmentPointer]['code'] === T_EQUAL) {
FixerHelper::change($phpcsFile, $previousVariablePointer, $assignmentPointer, 'return');
} else {
$phpcsFile->fixer->addContentBefore($previousVariablePointer, 'return ');
$phpcsFile->fixer->replaceToken($assignmentPointer, $assignmentFixerMapping[$tokens[$assignmentPointer]['code']]);
}
FixerHelper::removeBetweenIncluding($phpcsFile, $previousVariableSemicolonPointer + 1, $returnSemicolonPointer);
$phpcsFile->fixer->endChangeset();
}
private function findPreviousVariablePointer(File $phpcsFile, int $pointer, string $variableName): ?int
{
$tokens = $phpcsFile->getTokens();
for ($i = $pointer - 1; $i >= 0; $i--) {
if (
in_array($tokens[$i]['code'], TokenHelper::$functionTokenCodes, true)
&& ScopeHelper::isInSameScope($phpcsFile, $tokens[$i]['scope_opener'] + 1, $pointer)
) {
return null;
}
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $i - 1);
if ($tokens[$previousPointer]['code'] === T_DOUBLE_COLON) {
continue;
}
if (!ScopeHelper::isInSameScope($phpcsFile, $i, $pointer)) {
continue;
}
return $i;
}
return null;
}
private function isAssignedInControlStructure(File $phpcsFile, int $pointer): bool
{
$controlStructure = TokenHelper::findPrevious($phpcsFile, [
T_WHILE,
T_FOR,
T_FOREACH,
T_SWITCH,
T_IF,
T_ELSEIF,
], $pointer - 1);
if ($controlStructure === null) {
return false;
}
$tokens = $phpcsFile->getTokens();
return $tokens[$controlStructure]['parenthesis_opener'] < $pointer && $pointer < $tokens[$controlStructure]['parenthesis_closer'];
}
private function isAssignedInFunctionCall(File $phpcsFile, int $pointer): bool
{
$possibleFunctionNamePointer = TokenHelper::findPrevious($phpcsFile, T_STRING, $pointer - 1);
if ($possibleFunctionNamePointer === null) {
return false;
}
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $possibleFunctionNamePointer + 1);
if ($tokens[$parenthesisOpenerPointer]['code'] !== T_OPEN_PARENTHESIS) {
return false;
}
return $parenthesisOpenerPointer < $pointer && $pointer < $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
}
private function isAssignmentToVariable(File $phpcsFile, int $pointer): bool
{
$assignmentPointer = TokenHelper::findNextEffective($phpcsFile, $pointer + 1);
return in_array($phpcsFile->getTokens()[$assignmentPointer]['code'], [
T_EQUAL,
T_PLUS_EQUAL,
T_MINUS_EQUAL,
T_MUL_EQUAL,
T_DIV_EQUAL,
T_POW_EQUAL,
T_MOD_EQUAL,
T_AND_EQUAL,
T_OR_EQUAL,
T_XOR_EQUAL,
T_SL_EQUAL,
T_SR_EQUAL,
T_CONCAT_EQUAL,
], true);
}
private function findFunctionPointer(File $phpcsFile, int $pointer): ?int
{
$tokens = $phpcsFile->getTokens();
foreach (array_reverse($tokens[$pointer]['conditions'], true) as $conditionPointer => $conditionTokenCode) {
if (in_array($conditionTokenCode, TokenHelper::$functionTokenCodes, true)) {
return $conditionPointer;
}
}
return null;
}
private function isStaticVariable(File $phpcsFile, int $functionPointer, int $variablePointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
for ($i = $tokens[$functionPointer]['scope_opener'] + 1; $i < $variablePointer; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
$pointerBeforeParameter = TokenHelper::findPreviousEffective($phpcsFile, $i - 1);
if ($tokens[$pointerBeforeParameter]['code'] === T_STATIC) {
return true;
}
}
return false;
}
private function isFunctionParameter(File $phpcsFile, int $functionPointer, string $variableName): bool
{
$tokens = $phpcsFile->getTokens();
for ($i = $tokens[$functionPointer]['parenthesis_opener'] + 1; $i < $tokens[$functionPointer]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}
if ($tokens[$i]['content'] !== $variableName) {
continue;
}
return true;
}
return false;
}
private function isReturnedByReference(File $phpcsFile, int $functionPointer): bool
{
$tokens = $phpcsFile->getTokens();
$referencePointer = TokenHelper::findNextEffective($phpcsFile, $functionPointer + 1);
return $tokens[$referencePointer]['code'] === T_BITWISE_AND;
}
private function hasVariableVarAnnotation(File $phpcsFile, int $variablePointer): bool
{
$tokens = $phpcsFile->getTokens();
$pointerBeforeVariable = TokenHelper::findPreviousNonWhitespace($phpcsFile, $variablePointer - 1);
if ($tokens[$pointerBeforeVariable]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
return false;
}
$docCommentContent = TokenHelper::getContent($phpcsFile, $tokens[$pointerBeforeVariable]['comment_opener'], $pointerBeforeVariable);
return preg_match(
'~@(?:(?:phpstan|psalm)-)?var\\s+.+\\s+' . preg_quote($tokens[$variablePointer]['content'], '~') . '(?:\\s|$)~',
$docCommentContent
) !== 0;
}
private function hasAnotherAssignmentBefore(File $phpcsFile, int $variablePointer, string $variableName): bool
{
$previousVariablePointer = $this->findPreviousVariablePointer($phpcsFile, $variablePointer, $variableName);
if ($previousVariablePointer === null) {
return false;
}
if (!$this->isAssignmentToVariable($phpcsFile, $previousVariablePointer)) {
return false;
}
return $this->areBothVariablesNearby($phpcsFile, $previousVariablePointer, $variablePointer);
}
private function areBothPointersNearby(File $phpcsFile, int $firstPointer, int $secondPointer): bool
{
$firstVariableSemicolonPointer = $this->findSemicolon($phpcsFile, $firstPointer);
$pointerAfterFirstVariableSemicolon = TokenHelper::findNextEffective($phpcsFile, $firstVariableSemicolonPointer + 1);
return $pointerAfterFirstVariableSemicolon === $secondPointer;
}
private function areBothVariablesNearby(File $phpcsFile, int $firstVariablePointer, int $secondVariablePointer): bool
{
if ($this->areBothPointersNearby($phpcsFile, $firstVariablePointer, $secondVariablePointer)) {
return true;
}
$tokens = $phpcsFile->getTokens();
$lastConditionPointer = array_reverse(array_keys($tokens[$firstVariablePointer]['conditions']))[0];
$lastConditionScopeCloserPointer = $tokens[$lastConditionPointer]['scope_closer'];
if ($tokens[$lastConditionPointer]['code'] === T_DO) {
$lastConditionScopeCloserPointer = TokenHelper::findNext($phpcsFile, T_SEMICOLON, $lastConditionScopeCloserPointer + 1);
}
return TokenHelper::findNextEffective($phpcsFile, $lastConditionScopeCloserPointer + 1) === $secondVariablePointer;
}
private function findSemicolon(File $phpcsFile, int $pointer): int
{
$tokens = $phpcsFile->getTokens();
$semicolonPointer = null;
for ($i = $pointer + 1; $i < count($tokens) - 1; $i++) {
if ($tokens[$i]['code'] !== T_SEMICOLON) {
continue;
}
if (!ScopeHelper::isInSameScope($phpcsFile, $pointer, $i)) {
continue;
}
$semicolonPointer = $i;
break;
}
/** @var int $semicolonPointer */
$semicolonPointer = $semicolonPointer;
return $semicolonPointer;
}
}