project_start/phpstorm-stubs/tests/AbstractBaseStubsTestCase.php

229 lines
9.6 KiB
PHP

<?php
declare(strict_types=1);
namespace StubTests;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\BinaryOp\BitwiseOr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\UnaryMinus;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use StubTests\Model\PHPClass;
use StubTests\Model\PHPConst;
use StubTests\Model\PHPEnum;
use StubTests\Model\PHPFunction;
use StubTests\Model\PHPInterface;
use StubTests\Parsers\ParserUtils;
use StubTests\TestData\Providers\PhpStormStubsSingleton;
use StubTests\TestData\Providers\ReflectionStubsSingleton;
use function array_filter;
use function array_pop;
use function property_exists;
use function strval;
abstract class AbstractBaseStubsTestCase extends TestCase
{
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
PhpStormStubsSingleton::getPhpStormStubs();
ReflectionStubsSingleton::getReflectionStubs();
}
/**
* @throws Exception|RuntimeException
*/
public static function getStringRepresentationOfDefaultParameterValue(mixed $defaultValue, PHPClass|PHPInterface $contextClass = null): float|bool|int|string|null
{
if ($defaultValue instanceof ConstFetch) {
$defaultValueName = (string)$defaultValue->name;
if ($defaultValueName !== 'false' && $defaultValueName !== 'true' && $defaultValueName !== 'null') {
$constant = PhpStormStubsSingleton::getPhpStormStubs()->getConstant($defaultValueName);
$value = $constant->value;
} else {
$value = $defaultValueName;
}
} elseif ($defaultValue instanceof String_ || $defaultValue instanceof LNumber || $defaultValue instanceof DNumber) {
$value = strval($defaultValue->value);
} elseif ($defaultValue instanceof BitwiseOr) {
if ($defaultValue->left instanceof ConstFetch && $defaultValue->right instanceof ConstFetch) {
$constants = array_filter(
PhpStormStubsSingleton::getPhpStormStubs()->getConstants(),
fn (PHPConst $const) => property_exists($defaultValue->left, 'name') &&
$const->name === (string)$defaultValue->left->name
);
/** @var PHPConst $leftConstant */
$leftConstant = array_pop($constants);
$constants = array_filter(
PhpStormStubsSingleton::getPhpStormStubs()->getConstants(),
fn (PHPConst $const) => property_exists($defaultValue->right, 'name') &&
$const->name === (string)$defaultValue->right->name
);
/** @var PHPConst $rightConstant */
$rightConstant = array_pop($constants);
$value = $leftConstant->value|$rightConstant->value;
} elseif ($defaultValue->left instanceof ClassConstFetch && $defaultValue->right instanceof ClassConstFetch){
$leftClass = $defaultValue->left->class->toString();
$rightClass = $defaultValue->right->class->toString();
$leftClass = PhpStormStubsSingleton::getPhpStormStubs()->getClass($leftClass);
$rightClass = PhpStormStubsSingleton::getPhpStormStubs()->getClass($rightClass);
if ($leftClass === null || $rightClass === null) {
throw new Exception("Class $leftClass->name or $rightClass->name not found in stubs");
}
$leftConstant = $leftClass->getConstant((string)$defaultValue->left->name);
$rightConstant = $rightClass->getConstant((string)$defaultValue->right->name);
$value = $leftConstant->value|$rightConstant->value;
}
} elseif ($defaultValue instanceof UnaryMinus && property_exists($defaultValue->expr, 'value')) {
$value = '-' . $defaultValue->expr->value;
} elseif ($defaultValue instanceof ClassConstFetch) {
$class = (string)$defaultValue->class;
if ($class === 'self' && $contextClass !== null) {
$class = $contextClass->name;
}
$parentClass = PhpStormStubsSingleton::getPhpStormStubs()->getEnum($class) ??
PhpStormStubsSingleton::getPhpStormStubs()->getClass($class) ??
PhpStormStubsSingleton::getPhpStormStubs()->getInterface($class);
if ($parentClass === null) {
throw new Exception("Class $class not found in stubs");
}
if ($parentClass instanceof PHPEnum) {
$value = $parentClass->name . "::" . $defaultValue->name;
} elseif ((string)$defaultValue->name === 'class') {
$value = (string)$defaultValue->class;
} else {
$constant = $parentClass->getConstant((string)$defaultValue->name);;
$value = $constant->value;
}
} elseif ($defaultValue === null) {
$value = "null";
} elseif (is_array($defaultValue) || $defaultValue instanceof Array_) {
$value = '[]';
} elseif ($defaultValue instanceof \UnitEnum){
$value = get_class($defaultValue) . "::" . $defaultValue->name;
} else {
$value = strval($defaultValue);
}
return $value;
}
public static function getParameterRepresentation(PHPFunction $function): string
{
$result = '';
foreach ($function->parameters as $parameter) {
$types = array_unique($parameter->typesFromSignature + Model\CommonUtils::flattenArray($parameter->typesFromAttribute, false));
if (!empty($types)) {
$result .= implode('|', $types) . ' ';
}
if ($parameter->is_passed_by_ref) {
$result .= '&';
}
if ($parameter->is_vararg) {
$result .= '...';
}
$result .= '$' . $parameter->name . ', ';
}
return rtrim($result, ', ');
}
/**
* @param PHPFunction[] $filtered
* @return PHPFunction[]
* @throws RuntimeException
*/
protected static function getDuplicatedFunctions(array $filtered): array
{
$duplicatedFunctions = array_filter($filtered, function (PHPFunction $value, int|string $key) {
$duplicatesOfFunction = self::getAllDuplicatesOfFunction($value->name);
$functionVersions[] = ParserUtils::getAvailableInVersions(
PhpStormStubsSingleton::getPhpStormStubs()->getFunction($value->name, shouldSuitCurrentPhpVersion: false)
);
array_push($functionVersions, ...array_values(array_map(
fn (PHPFunction $function) => ParserUtils::getAvailableInVersions($function),
$duplicatesOfFunction
)));
$hasDuplicates = false;
$current = array_pop($functionVersions);
$next = array_pop($functionVersions);
while ($next !== null) {
if (!empty(array_intersect($current, $next))) {
$hasDuplicates = true;
}
$current = array_merge($current, $next);
$next = array_pop($functionVersions);
}
return $hasDuplicates;
}, ARRAY_FILTER_USE_BOTH);
return array_unique(array_map(fn (PHPFunction $function) => $function->name, $duplicatedFunctions));
}
/**
* @return PHPFunction[]
*/
protected static function getAllDuplicatesOfFunction(?string $name): array
{
return array_filter(
PhpStormStubsSingleton::getPhpStormStubs()->getFunctions(),
fn ($duplicateValue, $duplicateKey) => $duplicateValue->name === $name && str_contains($duplicateKey, 'duplicated'),
ARRAY_FILTER_USE_BOTH
);
}
/**
* @param string[] $reflectionTypes
* @param string[] $typesFromSignature
*/
public static function isReflectionTypesMatchSignature(array $reflectionTypes, array $typesFromSignature): bool
{
return empty(array_merge(
array_diff($reflectionTypes, $typesFromSignature),
array_diff($typesFromSignature, $reflectionTypes)
));
}
/**
* @param string[] $reflectionTypes
* @param string[] $typesFromAttribute
*/
public static function isReflectionTypesExistInAttributes(array $reflectionTypes, array $typesFromAttribute): bool
{
return empty(array_merge(
array_diff($reflectionTypes, array_filter($typesFromAttribute, fn ($type) => !empty($type))),
array_diff(array_filter($typesFromAttribute, fn ($type) => !empty($type)), $reflectionTypes)
));
}
/**
* @param string[][] $typesFromAttribute
*/
public static function getStringRepresentationOfTypeHintsFromAttributes(array $typesFromAttribute): string
{
$resultString = '';
foreach ($typesFromAttribute as $types) {
$resultString .= '[' . implode('|', $types) . ']';
}
return $resultString;
}
/**
* @param string[] $typesToProcess
* @param string[] $resultArray
*/
public static function convertNullableTypesToUnion(array $typesToProcess, array &$resultArray)
{
array_walk($typesToProcess, function (string $type) use (&$resultArray) {
if (str_contains($type, '?')) {
array_push($resultArray, 'null', ltrim($type, '?'));
} else {
$resultArray[] = $type;
}
});
}
}