google_forms/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/YodaHelper.php

359 lines
9.4 KiB
PHP

<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Helpers;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use function array_fill_keys;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_reverse;
use function array_values;
use function count;
use function end;
use function implode;
use function in_array;
use function key;
use function reset;
use const T_ARRAY;
use const T_ARRAY_CAST;
use const T_BOOL_CAST;
use const T_BOOLEAN_AND;
use const T_BOOLEAN_OR;
use const T_CASE;
use const T_CLOSE_CURLY_BRACKET;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSE_SHORT_ARRAY;
use const T_COALESCE;
use const T_COLON;
use const T_COMMA;
use const T_COMMENT;
use const T_CONSTANT_ENCAPSED_STRING;
use const T_DNUMBER;
use const T_DOC_COMMENT;
use const T_DOUBLE_CAST;
use const T_DOUBLE_COLON;
use const T_FALSE;
use const T_FN_ARROW;
use const T_INLINE_ELSE;
use const T_INLINE_THEN;
use const T_INT_CAST;
use const T_LNUMBER;
use const T_LOGICAL_AND;
use const T_LOGICAL_OR;
use const T_LOGICAL_XOR;
use const T_MATCH_ARROW;
use const T_MINUS;
use const T_NS_SEPARATOR;
use const T_NULL;
use const T_OBJECT_CAST;
use const T_OPEN_PARENTHESIS;
use const T_OPEN_SHORT_ARRAY;
use const T_OPEN_TAG;
use const T_PLUS;
use const T_RETURN;
use const T_SEMICOLON;
use const T_STRING;
use const T_STRING_CAST;
use const T_TRUE;
use const T_UNSET_CAST;
use const T_VARIABLE;
use const T_WHITESPACE;
/**
* @internal
*/
class YodaHelper
{
private const DYNAMISM_VARIABLE = 999;
private const DYNAMISM_CONSTANT = 1;
private const DYNAMISM_FUNCTION_CALL = 998;
/**
* @param array<int, array<string, array<int, int|string>|int|string>> $leftSideTokens
* @param array<int, array<string, array<int, int|string>|int|string>> $rightSideTokens
*/
public static function fix(File $phpcsFile, array $leftSideTokens, array $rightSideTokens): void
{
$phpcsFile->fixer->beginChangeset();
self::replace($phpcsFile, $leftSideTokens, $rightSideTokens);
self::replace($phpcsFile, $rightSideTokens, $leftSideTokens);
$phpcsFile->fixer->endChangeset();
}
/**
* @param array<int, array<string, array<int, int|string>|int|string>> $tokens
* @return array<int, array<string, array<int, int|string>|int|string>>
*/
public static function getLeftSideTokens(array $tokens, int $comparisonTokenPointer): array
{
$parenthesisDepth = 0;
$shortArrayDepth = 0;
$examinedTokenPointer = $comparisonTokenPointer;
$sideTokens = [];
$stopTokenCodes = self::getStopTokenCodes();
while (true) {
$examinedTokenPointer--;
$examinedToken = $tokens[$examinedTokenPointer];
/** @var string|int $examinedTokenCode */
$examinedTokenCode = $examinedToken['code'];
if ($parenthesisDepth === 0 && $shortArrayDepth === 0 && isset($stopTokenCodes[$examinedTokenCode])) {
break;
}
if ($examinedTokenCode === T_CLOSE_SHORT_ARRAY) {
$shortArrayDepth++;
} elseif ($examinedTokenCode === T_OPEN_SHORT_ARRAY) {
if ($shortArrayDepth === 0) {
break;
}
$shortArrayDepth--;
}
if ($examinedTokenCode === T_CLOSE_PARENTHESIS) {
$parenthesisDepth++;
} elseif ($examinedTokenCode === T_OPEN_PARENTHESIS) {
if ($parenthesisDepth === 0) {
break;
}
$parenthesisDepth--;
}
$sideTokens[$examinedTokenPointer] = $examinedToken;
}
return self::trimWhitespaceTokens(array_reverse($sideTokens, true));
}
/**
* @param array<int, array<string, array<int, int|string>|int|string>> $tokens
* @return array<int, array<string, array<int, int|string>|int|string>>
*/
public static function getRightSideTokens(array $tokens, int $comparisonTokenPointer): array
{
$parenthesisDepth = 0;
$shortArrayDepth = 0;
$examinedTokenPointer = $comparisonTokenPointer;
$sideTokens = [];
$stopTokenCodes = self::getStopTokenCodes();
while (true) {
$examinedTokenPointer++;
$examinedToken = $tokens[$examinedTokenPointer];
/** @var string|int $examinedTokenCode */
$examinedTokenCode = $examinedToken['code'];
if ($parenthesisDepth === 0 && $shortArrayDepth === 0 && isset($stopTokenCodes[$examinedTokenCode])) {
break;
}
if ($examinedTokenCode === T_OPEN_SHORT_ARRAY) {
$shortArrayDepth++;
} elseif ($examinedTokenCode === T_CLOSE_SHORT_ARRAY) {
if ($shortArrayDepth === 0) {
break;
}
$shortArrayDepth--;
}
if ($examinedTokenCode === T_OPEN_PARENTHESIS) {
$parenthesisDepth++;
} elseif ($examinedTokenCode === T_CLOSE_PARENTHESIS) {
if ($parenthesisDepth === 0) {
break;
}
$parenthesisDepth--;
}
$sideTokens[$examinedTokenPointer] = $examinedToken;
}
return self::trimWhitespaceTokens($sideTokens);
}
/**
* @param array<int, array<string, array<int, int|string>|int|string>> $tokens
* @param array<int, array<string, array<int, int|string>|int|string>> $sideTokens
*/
public static function getDynamismForTokens(array $tokens, array $sideTokens): ?int
{
$sideTokens = array_values(array_filter($sideTokens, static function (array $token): bool {
return !in_array(
$token['code'],
[T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_NS_SEPARATOR, T_PLUS, T_MINUS, T_INT_CAST, T_DOUBLE_CAST, T_STRING_CAST, T_ARRAY_CAST, T_OBJECT_CAST, T_BOOL_CAST, T_UNSET_CAST],
true
);
}));
$sideTokensCount = count($sideTokens);
$dynamism = self::getTokenDynamism();
if ($sideTokensCount > 0) {
if ($sideTokens[0]['code'] === T_VARIABLE) {
// Expression starts with a variable - wins over everything else
return self::DYNAMISM_VARIABLE;
}
if ($sideTokens[$sideTokensCount - 1]['code'] === T_CLOSE_PARENTHESIS) {
if (array_key_exists('parenthesis_owner', $sideTokens[$sideTokensCount - 1])) {
/** @var int $parenthesisOwner */
$parenthesisOwner = $sideTokens[$sideTokensCount - 1]['parenthesis_owner'];
if ($tokens[$parenthesisOwner]['code'] === T_ARRAY) {
// Array
return $dynamism[T_ARRAY];
}
}
// Function or method call
return self::DYNAMISM_FUNCTION_CALL;
}
if ($sideTokensCount === 1 && $sideTokens[0]['code'] === T_STRING) {
// Constant
return self::DYNAMISM_CONSTANT;
}
}
if ($sideTokensCount > 2 && $sideTokens[$sideTokensCount - 2]['code'] === T_DOUBLE_COLON) {
if ($sideTokens[$sideTokensCount - 1]['code'] === T_VARIABLE) {
// Static property access
return self::DYNAMISM_VARIABLE;
}
if ($sideTokens[$sideTokensCount - 1]['code'] === T_STRING) {
// Class constant
return self::DYNAMISM_CONSTANT;
}
}
if (array_key_exists(0, $sideTokens)) {
/** @var int $sideTokenCode */
$sideTokenCode = $sideTokens[0]['code'];
if (array_key_exists($sideTokenCode, $dynamism)) {
return $dynamism[$sideTokenCode];
}
}
return null;
}
/**
* @param array<int, array<string, array<int, int|string>|int|string>> $tokens
* @return array<int, array<string, array<int, int|string>|int|string>>
*/
public static function trimWhitespaceTokens(array $tokens): array
{
foreach ($tokens as $pointer => $token) {
if ($token['code'] !== T_WHITESPACE) {
break;
}
unset($tokens[$pointer]);
}
foreach (array_reverse($tokens, true) as $pointer => $token) {
if ($token['code'] !== T_WHITESPACE) {
break;
}
unset($tokens[$pointer]);
}
return $tokens;
}
/**
* @param array<int, array<string, array<int, int|string>|int|string>> $oldTokens
* @param array<int, array<string, array<int, int|string>|int|string>> $newTokens
*/
private static function replace(File $phpcsFile, array $oldTokens, array $newTokens): void
{
reset($oldTokens);
/** @var int $firstOldPointer */
$firstOldPointer = key($oldTokens);
end($oldTokens);
/** @var int $lastOldPointer */
$lastOldPointer = key($oldTokens);
$content = implode('', array_map(static function (array $token): string {
/** @var string $content */
$content = $token['content'];
return $content;
}, $newTokens));
FixerHelper::change($phpcsFile, $firstOldPointer, $lastOldPointer, $content);
}
/**
* @return array<int|string, int>
*/
private static function getTokenDynamism(): array
{
static $tokenDynamism;
if ($tokenDynamism === null) {
$tokenDynamism = [
T_TRUE => 0,
T_FALSE => 0,
T_NULL => 0,
T_DNUMBER => 0,
T_LNUMBER => 0,
T_OPEN_SHORT_ARRAY => 0,
// Do not stack error messages when the old-style array syntax is used
T_ARRAY => 0,
T_CONSTANT_ENCAPSED_STRING => 0,
T_VARIABLE => self::DYNAMISM_VARIABLE,
T_STRING => self::DYNAMISM_FUNCTION_CALL,
];
$tokenDynamism += array_fill_keys(array_keys(Tokens::$castTokens), 3);
}
return $tokenDynamism;
}
/**
* @return array<int|string, bool>
*/
private static function getStopTokenCodes(): array
{
static $stopTokenCodes;
if ($stopTokenCodes === null) {
$stopTokenCodes = [
T_BOOLEAN_AND => true,
T_BOOLEAN_OR => true,
T_SEMICOLON => true,
T_OPEN_TAG => true,
T_INLINE_THEN => true,
T_INLINE_ELSE => true,
T_LOGICAL_AND => true,
T_LOGICAL_OR => true,
T_LOGICAL_XOR => true,
T_COALESCE => true,
T_CASE => true,
T_COLON => true,
T_RETURN => true,
T_COMMA => true,
T_CLOSE_CURLY_BRACKET => true,
T_MATCH_ARROW => true,
T_FN_ARROW => true,
];
$stopTokenCodes += array_fill_keys(array_keys(Tokens::$assignmentTokens), true);
$stopTokenCodes += array_fill_keys(array_keys(Tokens::$commentTokens), true);
}
return $stopTokenCodes;
}
}