JFIF x x C C " } !1AQa "q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w !1AQ aq"2B #3Rbr{
File "Annotations.php"
Full Path: /home/u735268861/domains/palsarh.in/public_html/vendor/cuyz/valinor/src/Utility/Reflection/Annotations.php
File size: 5.33 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Utility\Reflection;
use CuyZ\Valinor\Type\Parser\Lexer\TokenizedAnnotation;
use CuyZ\Valinor\Type\Parser\Lexer\TokensExtractor;
use ReflectionClass;
use ReflectionFunctionAbstract;
use ReflectionProperty;
use function array_filter;
use function array_merge;
use function array_values;
use function current;
use function in_array;
use function trim;
/** @internal */
final class Annotations
{
/** @var list<TokenizedAnnotation> */
private array $annotations = [];
private function __construct(string|false $docBlock)
{
if ($docBlock === false) {
return;
}
$docBlock = $this->sanitizeDocComment($docBlock);
$tokens = (new TokensExtractor($docBlock))->all();
$current = [];
while (($token = array_pop($tokens)) !== null) {
if (str_starts_with($token, '@')) {
$current = $this->trimArrayTips($current);
if ($current !== []) {
array_unshift($this->annotations, new TokenizedAnnotation($token, $current));
}
$current = [];
} else {
array_unshift($current, $token);
}
}
}
/**
* @param class-string $className
* @return list<TokenizedAnnotation>
*/
public static function forImportTypes(string $className): array
{
return (new self(Reflection::class($className)->getDocComment()))->filteredByPriority(
'@phpstan-import-type',
'@psalm-import-type',
);
}
/**
* @param ReflectionClass<covariant object>|ReflectionFunctionAbstract $reflection
* @return list<TokenizedAnnotation>
*/
public static function forTemplates(ReflectionClass|ReflectionFunctionAbstract $reflection): array
{
return (new self($reflection->getDocComment()))->filteredByPriority(
'@phpstan-template',
'@psalm-template',
'@template',
);
}
/**
* @param class-string $className
* @return list<TokenizedAnnotation>
*/
public static function forParents(string $className): array
{
return (new self(Reflection::class($className)->getDocComment()))->filteredByPriority(
'@phpstan-extends',
'@psalm-extends',
'@extends',
);
}
/**
* @param class-string $className
* @return list<TokenizedAnnotation>
*/
public static function forLocalAliases(string $className): array
{
return (new self(Reflection::class($className)->getDocComment()))->filteredInOrder(
'@phpstan-type',
'@psalm-type',
);
}
/**
* @return list<TokenizedAnnotation>
*/
public static function forParameters(ReflectionFunctionAbstract $function): array
{
return (new self($function->getDocComment()))->filteredByPriority(
'@phpstan-param',
'@psalm-param',
'@param',
);
}
public static function forProperty(ReflectionProperty $function): ?string
{
return (new self($function->getDocComment()))->firstOf(
'@phpstan-var',
'@psalm-var',
'@var',
)?->raw();
}
public static function forFunctionReturnType(ReflectionFunctionAbstract $function): ?string
{
return (new self($function->getDocComment()))->firstOf(
'@phpstan-return',
'@psalm-return',
'@return',
)?->raw();
}
private function firstOf(string ...$allowed): ?TokenizedAnnotation
{
foreach ($allowed as $annotation) {
foreach ($this->annotations as $tokenizedAnnotation) {
if ($tokenizedAnnotation->name() === $annotation) {
return $tokenizedAnnotation;
}
}
}
return null;
}
/**
* @return list<TokenizedAnnotation>
*/
private function filteredInOrder(string ...$allowed): array
{
return array_values(array_filter(
$this->annotations,
static fn (TokenizedAnnotation $tokenizedAnnotation) => in_array($tokenizedAnnotation->name(), $allowed, true),
));
}
/**
* @return list<TokenizedAnnotation>
*/
private function filteredByPriority(string ...$allowed): array
{
$result = [];
foreach ($allowed as $annotation) {
$filtered = array_filter(
$this->annotations,
static fn (TokenizedAnnotation $tokenizedAnnotation) => $tokenizedAnnotation->name() === $annotation,
);
$result = array_merge($result, $filtered);
}
return $result;
}
private function sanitizeDocComment(string $value): string
{
$value = preg_replace('#^\s*/\*\*([^/]+)\*/\s*$#', '$1', $value);
return preg_replace('/^\s*\*\s*(\S*)/mU', '$1', $value); // @phpstan-ignore-line / We know the regex is correct
}
/**
* @param list<string> $array
* @return list<string>
*/
private function trimArrayTips(array $array): array
{
if ($array !== [] && trim(current($array)) === '') {
array_shift($array);
}
if ($array !== [] && trim(end($array)) === '') {
array_pop($array);
}
return $array;
}
}