|int|string>> $leftSideTokens * @param array|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|string>> $tokens * @return array|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|string>> $tokens * @return array|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|string>> $tokens * @param array|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|string>> $tokens * @return array|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|string>> $oldTokens * @param array|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 */ 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 */ 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; } }