File: /home/slfopp7cb1df/public_html/sitepacket.com/system/system/ThirdParty/Kint/CallFinder.php
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Jonathan Vollebregt (jnvsor@gmail.com), Rokas Šleinius (raveren@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Kint;
/**
* @psalm-type PhpTokenArray = array{int, string, int}
* @psalm-type PhpToken = string|PhpTokenArray
* @psalm-type CallParameter = array{
* name: string,
* path: string,
* expression: bool,
* literal: bool,
* new_without_parens: bool,
* }
*/
class CallFinder
{
private static array $ignore = [
T_CLOSE_TAG => true,
T_COMMENT => true,
T_DOC_COMMENT => true,
T_INLINE_HTML => true,
T_OPEN_TAG => true,
T_OPEN_TAG_WITH_ECHO => true,
T_WHITESPACE => true,
];
/**
* Things we need to do specially for operator tokens:
* - Refuse to strip spaces around them
* - Wrap the access path in parentheses if there
* are any of these in the final short parameter.
*/
private static array $operator = [
T_AND_EQUAL => true,
T_BOOLEAN_AND => true,
T_BOOLEAN_OR => true,
T_ARRAY_CAST => true,
T_BOOL_CAST => true,
T_CLONE => true,
T_CONCAT_EQUAL => true,
T_DEC => true,
T_DIV_EQUAL => true,
T_DOUBLE_CAST => true,
T_FUNCTION => true,
T_INC => true,
T_INCLUDE => true,
T_INCLUDE_ONCE => true,
T_INSTANCEOF => true,
T_INT_CAST => true,
T_IS_EQUAL => true,
T_IS_GREATER_OR_EQUAL => true,
T_IS_IDENTICAL => true,
T_IS_NOT_EQUAL => true,
T_IS_NOT_IDENTICAL => true,
T_IS_SMALLER_OR_EQUAL => true,
T_LOGICAL_AND => true,
T_LOGICAL_OR => true,
T_LOGICAL_XOR => true,
T_MINUS_EQUAL => true,
T_MOD_EQUAL => true,
T_MUL_EQUAL => true,
T_OBJECT_CAST => true,
T_OR_EQUAL => true,
T_PLUS_EQUAL => true,
T_REQUIRE => true,
T_REQUIRE_ONCE => true,
T_SL => true,
T_SL_EQUAL => true,
T_SR => true,
T_SR_EQUAL => true,
T_STRING_CAST => true,
T_UNSET_CAST => true,
T_XOR_EQUAL => true,
T_POW => true,
T_POW_EQUAL => true,
T_SPACESHIP => true,
T_DOUBLE_ARROW => true,
T_FN => true,
T_COALESCE_EQUAL => true,
'!' => true,
'%' => true,
'&' => true,
'*' => true,
'+' => true,
'-' => true,
'.' => true,
'/' => true,
':' => true,
'<' => true,
'=' => true,
'>' => true,
'?' => true,
'^' => true,
'|' => true,
'~' => true,
];
private static array $preserve_spaces = [
T_CLASS => true,
T_NEW => true,
];
private static array $strip = [
'(' => true,
')' => true,
'[' => true,
']' => true,
'{' => true,
'}' => true,
T_OBJECT_OPERATOR => true,
T_DOUBLE_COLON => true,
T_NS_SEPARATOR => true,
];
private static array $classcalls = [
T_DOUBLE_COLON => true,
T_OBJECT_OPERATOR => true,
];
private static array $namespace = [
T_STRING => true,
];
/**
* @psalm-param callable-array|callable-string $function
*
* @psalm-return list<array{parameters: list<CallParameter>, modifiers: list<PhpToken>}>
*
* @return array List of matching calls on the relevant line
*/
public static function getFunctionCalls(string $source, int $line, $function): array
{
static $up = [
'(' => true,
'[' => true,
'{' => true,
T_CURLY_OPEN => true,
T_DOLLAR_OPEN_CURLY_BRACES => true,
];
static $down = [
')' => true,
']' => true,
'}' => true,
];
static $modifiers = [
'!' => true,
'@' => true,
'~' => true,
'+' => true,
'-' => true,
];
static $identifier = [
T_DOUBLE_COLON => true,
T_STRING => true,
T_NS_SEPARATOR => true,
];
if (KINT_PHP80) {
$up[T_ATTRIBUTE] = true;
self::$operator[T_MATCH] = true;
self::$strip[T_NULLSAFE_OBJECT_OPERATOR] = true;
self::$classcalls[T_NULLSAFE_OBJECT_OPERATOR] = true;
self::$namespace[T_NAME_FULLY_QUALIFIED] = true;
self::$namespace[T_NAME_QUALIFIED] = true;
self::$namespace[T_NAME_RELATIVE] = true;
$identifier[T_NAME_FULLY_QUALIFIED] = true;
$identifier[T_NAME_QUALIFIED] = true;
$identifier[T_NAME_RELATIVE] = true;
}
if (!KINT_PHP84) {
self::$operator[T_NEW] = true; // @codeCoverageIgnore
}
/** @psalm-var list<PhpToken> */
$tokens = \token_get_all($source);
$function_calls = [];
// Performance optimization preventing backwards loops
/** @psalm-var array<PhpToken|null> */
$prev_tokens = [null, null, null];
if (\is_array($function)) {
$class = \explode('\\', $function[0]);
$class = \strtolower(\end($class));
$function = \strtolower($function[1]);
} else {
$class = null;
/**
* @psalm-suppress RedundantFunctionCallGivenDocblockType
* Psalm bug #11075
*/
$function = \strtolower($function);
}
// Loop through tokens
foreach ($tokens as $index => $token) {
if (!\is_array($token)) {
continue;
}
if ($token[2] > $line) {
break;
}
// Store the last real tokens for later
if (isset(self::$ignore[$token[0]])) {
continue;
}
$prev_tokens = [$prev_tokens[1], $prev_tokens[2], $token];
// The logic for 7.3 through 8.1 is far more complicated.
// This should speed things up without making a lot more work for us
if (KINT_PHP82 && $line !== $token[2]) {
continue;
}
// Check if it's the right type to be the function we're looking for
if (!isset(self::$namespace[$token[0]])) {
continue;
}
$ns = \explode('\\', \strtolower($token[1]));
if (\end($ns) !== $function) {
continue;
}
// Check if it's a function call
$nextReal = self::realTokenIndex($tokens, $index);
if ('(' !== ($tokens[$nextReal] ?? null)) {
continue;
}
// Check if it matches the signature
if (null === $class) {
if (null !== $prev_tokens[1] && isset(self::$classcalls[$prev_tokens[1][0]])) {
continue;
}
} else {
if (null === $prev_tokens[1] || T_DOUBLE_COLON !== $prev_tokens[1][0]) {
continue;
}
if (null === $prev_tokens[0] || !isset(self::$namespace[$prev_tokens[0][0]])) {
continue;
}
// All self::$namespace tokens are T_ constants
/**
* @psalm-var PhpTokenArray $prev_tokens[0]
* Psalm bug #746 (wontfix)
*/
$ns = \explode('\\', \strtolower($prev_tokens[0][1]));
if (\end($ns) !== $class) {
continue;
}
}
$last_line = $token[2];
$depth = 1; // The depth respective to the function call
$offset = $nextReal + 1; // The start of the function call
$instring = false; // Whether we're in a string or not
$realtokens = false; // Whether the current scope contains anything meaningful or not
$paramrealtokens = false; // Whether the current parameter contains anything meaningful
$params = []; // All our collected parameters
$shortparam = []; // The short version of the parameter
$param_start = $offset; // The distance to the start of the parameter
// Loop through the following tokens until the function call ends
while (isset($tokens[$offset])) {
$token = $tokens[$offset];
if (\is_array($token)) {
$last_line = $token[2];
}
if (!isset(self::$ignore[$token[0]]) && !isset($down[$token[0]])) {
$paramrealtokens = $realtokens = true;
}
// If it's a token that makes us to up a level, increase the depth
if (isset($up[$token[0]])) {
if (1 === $depth) {
$shortparam[] = $token;
$realtokens = false;
}
++$depth;
} elseif (isset($down[$token[0]])) {
--$depth;
// If this brings us down to the parameter level, and we've had
// real tokens since going up, fill the $shortparam with an ellipsis
if (1 === $depth) {
if ($realtokens) {
$shortparam[] = '...';
}
$shortparam[] = $token;
}
} elseif ('"' === $token || 'b"' === $token) {
// Strings use the same symbol for up and down, but we can
// only ever be inside one string, so just use a bool for that
if ($instring) {
--$depth;
if (1 === $depth) {
$shortparam[] = '...';
}
} else {
++$depth;
}
$instring = !$instring;
$shortparam[] = $token;
} elseif (1 === $depth) {
if (',' === $token[0]) {
$params[] = [
'full' => \array_slice($tokens, $param_start, $offset - $param_start),
'short' => $shortparam,
];
$shortparam = [];
$paramrealtokens = false;
$param_start = $offset + 1;
} elseif (T_CONSTANT_ENCAPSED_STRING === $token[0]) {
$quote = $token[1][0];
if ('b' === $quote) {
$quote = $token[1][1];
if (\strlen($token[1]) > 3) {
$token[1] = 'b'.$quote.'...'.$quote;
}
} else {
if (\strlen($token[1]) > 2) {
$token[1] = $quote.'...'.$quote;
}
}
$shortparam[] = $token;
} else {
$shortparam[] = $token;
}
}
// Depth has dropped to 0 (So we've hit the closing paren)
if ($depth <= 0) {
if ($paramrealtokens) {
$params[] = [
'full' => \array_slice($tokens, $param_start, $offset - $param_start),
'short' => $shortparam,
];
}
break;
}
++$offset;
}
// If we're not passed (or at) the line at the end
// of the function call, we're too early so skip it
// Only applies to < 8.2 since we check line explicitly above that
if (!KINT_PHP82 && $last_line < $line) {
continue; // @codeCoverageIgnore
}
$formatted_parameters = [];
// Format the final output parameters
foreach ($params as $param) {
$name = self::tokensFormatted($param['short']);
$path = self::tokensToString(self::tokensTrim($param['full']));
$expression = false;
$literal = false;
$new_without_parens = false;
foreach ($name as $token) {
if (self::tokenIsOperator($token)) {
$expression = true;
break;
}
}
// As of 8.4 new is only an expression when parentheses are
// omitted. In that case we can cheat and add them ourselves.
//
// > PHP interprets the first expression after new as a class name
// per https://wiki.php.net/rfc/new_without_parentheses
if (KINT_PHP84 && !$expression && T_NEW === $name[0][0]) {
$had_name_token = false;
$new_without_parens = true;
foreach ($name as $token) {
if (T_NEW === $token[0]) {
continue;
}
if (isset(self::$ignore[$token[0]])) {
continue;
}
if (T_CLASS === $token[0]) {
$new_without_parens = false;
break;
}
if ('(' === $token && $had_name_token) {
$new_without_parens = false;
break;
}
$had_name_token = true;
}
}
if (!$expression && 1 === \count($name)) {
switch ($name[0][0]) {
case T_CONSTANT_ENCAPSED_STRING:
case T_LNUMBER:
case T_DNUMBER:
$literal = true;
break;
case T_STRING:
switch (\strtolower($name[0][1])) {
case 'null':
case 'true':
case 'false':
$literal = true;
}
}
$name = self::tokensToString($name);
} else {
$name = self::tokensToString($name);
if (!$expression) {
switch (\strtolower($name)) {
case 'array()':
case '[]':
$literal = true;
break;
}
}
}
$formatted_parameters[] = [
'name' => $name,
'path' => $path,
'expression' => $expression,
'literal' => $literal,
'new_without_parens' => $new_without_parens,
];
}
// Skip first-class callables
if (KINT_PHP81 && 1 === \count($formatted_parameters) && '...' === \reset($formatted_parameters)['path']) {
continue;
}
// Get the modifiers
--$index;
while (isset($tokens[$index])) {
if (!isset(self::$ignore[$tokens[$index][0]]) && !isset($identifier[$tokens[$index][0]])) {
break;
}
--$index;
}
$mods = [];
while (isset($tokens[$index])) {
if (isset(self::$ignore[$tokens[$index][0]])) {
--$index;
continue;
}
if (isset($modifiers[$tokens[$index][0]])) {
$mods[] = $tokens[$index];
--$index;
continue;
}
break;
}
$function_calls[] = [
'parameters' => $formatted_parameters,
'modifiers' => $mods,
];
}
return $function_calls;
}
private static function realTokenIndex(array $tokens, int $index): ?int
{
++$index;
while (isset($tokens[$index])) {
if (!isset(self::$ignore[$tokens[$index][0]])) {
return $index;
}
++$index;
}
return null;
}
/**
* We need a separate method to check if tokens are operators because we
* occasionally add "..." to short parameter versions. If we simply check
* for `$token[0]` then "..." will incorrectly match the "." operator.
*
* @psalm-param PhpToken $token The token to check
*/
private static function tokenIsOperator($token): bool
{
return '...' !== $token && isset(self::$operator[$token[0]]);
}
/**
* @psalm-param PhpToken $token The token to check
*/
private static function tokenPreserveWhitespace($token): bool
{
return self::tokenIsOperator($token) || isset(self::$preserve_spaces[$token[0]]);
}
private static function tokensToString(array $tokens): string
{
$out = '';
foreach ($tokens as $token) {
if (\is_string($token)) {
$out .= $token;
} else {
$out .= $token[1];
}
}
return $out;
}
private static function tokensTrim(array $tokens): array
{
foreach ($tokens as $index => $token) {
if (isset(self::$ignore[$token[0]])) {
unset($tokens[$index]);
} else {
break;
}
}
$tokens = \array_reverse($tokens);
foreach ($tokens as $index => $token) {
if (isset(self::$ignore[$token[0]])) {
unset($tokens[$index]);
} else {
break;
}
}
return \array_reverse($tokens);
}
private static function tokensFormatted(array $tokens): array
{
$tokens = self::tokensTrim($tokens);
$space = false;
$attribute = false;
// Keep space between "strip" symbols for different behavior for matches or closures
// Normally we want to strip spaces between strip tokens: $x{...}[...]
// However with closures and matches we don't: function (...) {...}
$ignorestrip = false;
$output = [];
$last = null;
if (T_FUNCTION === $tokens[0][0] ||
T_FN === $tokens[0][0] ||
(KINT_PHP80 && T_MATCH === $tokens[0][0])
) {
$ignorestrip = true;
}
foreach ($tokens as $index => $token) {
if (isset(self::$ignore[$token[0]])) {
if ($space) {
continue;
}
$next = self::realTokenIndex($tokens, $index);
if (null === $next) {
// This should be impossible, since we always call tokensTrim first
break; // @codeCoverageIgnore
}
$next = $tokens[$next];
/**
* @psalm-var PhpToken $last
* Since we call tokensTrim we know we can't be here without a $last
*/
if ($attribute && ']' === $last[0]) {
$attribute = false;
} elseif (!$ignorestrip && isset(self::$strip[$last[0]]) && !self::tokenPreserveWhitespace($next)) {
continue;
}
if (!$ignorestrip && isset(self::$strip[$next[0]]) && !self::tokenPreserveWhitespace($last)) {
continue;
}
$token[1] = ' ';
$space = true;
} else {
if (KINT_PHP80 && null !== $last && T_ATTRIBUTE === $last[0]) {
$attribute = true;
}
$space = false;
$last = $token;
}
$output[] = $token;
}
return $output;
}
}