update lock clucknut
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / build-and-push (push) Successful in 3m14s
Build, Push and Deploy / deploy-staging (push) Successful in 25s
Build, Push and Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-18 20:32:18 +07:00
parent 4554035227
commit dcaf267458
3359 changed files with 153185 additions and 205489 deletions

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -13,6 +13,7 @@
use Psy\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* A pretty-printer for code.
@@ -28,14 +29,19 @@ class CodeFormatter implements ReflectorFormatter
const HIGHLIGHT_PUBLIC = 'public';
const HIGHLIGHT_PROTECTED = 'protected';
const HIGHLIGHT_PRIVATE = 'private';
const HIGHLIGHT_CLASS = 'class';
const HIGHLIGHT_BOOL = 'bool';
const HIGHLIGHT_CONST = 'const';
const HIGHLIGHT_NUMBER = 'number';
const HIGHLIGHT_STRING = 'string';
const HIGHLIGHT_ARRAY_KEY = 'array_key';
const HIGHLIGHT_COMMENT = 'code_comment';
const HIGHLIGHT_INLINE_HTML = 'inline_html';
private const TOKEN_MAP = [
private const CLASS_REFERENCE_KEYWORDS = [\T_NEW, \T_INSTANCEOF, \T_CATCH];
private const BASE_TOKEN_MAP = [
// Not highlighted
\T_OPEN_TAG => self::HIGHLIGHT_DEFAULT,
\T_OPEN_TAG_WITH_ECHO => self::HIGHLIGHT_DEFAULT,
@@ -73,6 +79,71 @@ class CodeFormatter implements ReflectorFormatter
\T_INLINE_HTML => self::HIGHLIGHT_INLINE_HTML,
];
private const KEYWORD_TOKENS = [
\T_ABSTRACT,
\T_ARRAY,
\T_AS,
\T_BREAK,
\T_CALLABLE,
\T_CASE,
\T_CATCH,
\T_CLONE,
\T_CONTINUE,
\T_DECLARE,
\T_DEFAULT,
\T_DO,
\T_ECHO,
\T_ELSE,
\T_ELSEIF,
\T_EMPTY,
\T_ENDDECLARE,
\T_ENDFOR,
\T_ENDFOREACH,
\T_ENDIF,
\T_ENDSWITCH,
\T_ENDWHILE,
\T_EVAL,
\T_EXIT,
\T_EXTENDS,
\T_FINAL,
\T_FINALLY,
\T_FN,
\T_FOR,
\T_FOREACH,
\T_FUNCTION,
\T_GLOBAL,
\T_GOTO,
\T_IF,
\T_IMPLEMENTS,
\T_INCLUDE,
\T_INCLUDE_ONCE,
\T_INSTANCEOF,
\T_INSTEADOF,
\T_INTERFACE,
\T_ISSET,
\T_LIST,
\T_LOGICAL_AND,
\T_LOGICAL_OR,
\T_LOGICAL_XOR,
\T_NAMESPACE,
\T_NEW,
\T_PRINT,
\T_REQUIRE,
\T_REQUIRE_ONCE,
\T_RETURN,
\T_STATIC,
\T_SWITCH,
\T_THROW,
\T_TRAIT,
\T_TRY,
\T_UNSET,
\T_USE,
\T_VAR,
\T_WHILE,
\T_YIELD,
\T_YIELD_FROM,
];
/**
* Format the code represented by $reflector for shell output.
*
@@ -115,6 +186,52 @@ public static function formatCode(string $code, int $startLine = 1, ?int $endLin
return \implode('', \iterator_to_array($lines));
}
/**
* Format a PHP snippet into ANSI-safe lines for inline shell rendering.
*
* Styles are applied line-by-line so multiline tokens do not leak styling
* across prompt boundaries.
*
* @return string[]
*/
public static function formatInputLines(string $code, ?OutputFormatterInterface $formatter = null): array
{
$lines = [''];
$lineIndex = 0;
$first = true;
foreach (self::tokenizeSpans('<?php '.$code) as [$spanType, $spanText]) {
if ($first) {
$spanText = (string) \substr($spanText, \strlen('<?php '));
$first = false;
if ($spanText === '') {
continue;
}
}
$parts = \preg_split('/(\r\n?|\n)/', $spanText, -1, \PREG_SPLIT_DELIM_CAPTURE);
if ($parts === false) {
$parts = [$spanText];
}
foreach ($parts as $part) {
if ($part === "\r" || $part === "\n" || $part === "\r\n") {
$lines[++$lineIndex] = '';
continue;
}
if ($part === '') {
continue;
}
$lines[$lineIndex] .= self::formatInputSpan($spanType, $part, $formatter);
}
}
return $lines;
}
/**
* Get the start line for a given Reflector.
*
@@ -149,11 +266,22 @@ private static function getStartLine(\Reflector $reflector): int
*/
private static function tokenizeSpans(string $code): \Generator
{
$tokens = \token_get_all($code);
$arrayKeyIndexes = self::findArrayKeyTokenIndexes($tokens);
[$classNameIndexes, $keywordNameIndexes] = self::findClassNameTokenIndexes($tokens);
$spanType = null;
$buffer = '';
foreach (\token_get_all($code) as $token) {
$nextType = self::nextHighlightType($token, $spanType);
foreach ($tokens as $index => $token) {
if (isset($arrayKeyIndexes[$index])) {
$nextType = self::HIGHLIGHT_ARRAY_KEY;
} elseif (isset($keywordNameIndexes[$index])) {
$nextType = self::HIGHLIGHT_KEYWORD;
} elseif (isset($classNameIndexes[$index])) {
$nextType = self::HIGHLIGHT_CLASS;
} else {
$nextType = self::nextHighlightType($token);
}
$spanType = $spanType ?: $nextType;
if ($spanType !== $nextType) {
@@ -171,30 +299,408 @@ private static function tokenizeSpans(string $code): \Generator
}
/**
* Given a token and the current highlight span type, compute the next type.
* Given a token, compute the highlight type from the token map.
*
* @param array|string $token \token_get_all token
* @param string|null $currentType
* @param array|string $token \token_get_all token
*
* @return string|null
*/
private static function nextHighlightType($token, $currentType)
private static function nextHighlightType($token)
{
if ($token === '"') {
return self::HIGHLIGHT_STRING;
}
if (\is_array($token)) {
if ($token[0] === \T_WHITESPACE) {
return $currentType;
if (($literalHighlight = self::literalHighlightType($token)) !== null) {
return $literalHighlight;
}
if (\array_key_exists($token[0], self::TOKEN_MAP)) {
return self::TOKEN_MAP[$token[0]];
$tokenMap = self::getTokenMap();
if (\array_key_exists($token[0], $tokenMap)) {
return $tokenMap[$token[0]];
}
}
return self::HIGHLIGHT_KEYWORD;
return self::HIGHLIGHT_DEFAULT;
}
/**
* Build the token-to-style map once so unsupported token constants on older
* PHP versions can be ignored safely.
*
* @return string[]
*/
private static function getTokenMap(): array
{
static $tokenMap = null;
if ($tokenMap !== null) {
return $tokenMap;
}
$tokenMap = self::BASE_TOKEN_MAP;
foreach (self::KEYWORD_TOKENS as $tokenType) {
$tokenMap[$tokenType] = self::HIGHLIGHT_KEYWORD;
}
if (\defined('T_MATCH')) {
$tokenMap[\constant('T_MATCH')] = self::HIGHLIGHT_KEYWORD;
}
return $tokenMap;
}
/**
* Mark string literal tokens that are serving as array keys.
*
* This stays token-based instead of requiring a valid AST so it can work
* during interactive editing on incomplete input.
*
* @param array<int, array|string> $tokens
*
* @return array<int, true>
*/
private static function findArrayKeyTokenIndexes(array $tokens): array
{
$arrayKeyIndexes = [];
$stack = [];
$pendingArrayParen = false;
foreach ($tokens as $index => $token) {
if (\is_array($token)) {
if ($token[0] === \T_ARRAY) {
$pendingArrayParen = true;
}
if ($token[0] === \T_CONSTANT_ENCAPSED_STRING && self::isArrayContext($stack) && self::nextSignificantTokenIsDoubleArrow($tokens, $index)) {
$arrayKeyIndexes[$index] = true;
}
if (!self::isIgnorableToken($token) && $token[0] !== \T_ARRAY) {
$pendingArrayParen = false;
}
continue;
}
if ($token === '(') {
$stack[] = [
'closing' => ')',
'arrayLike' => $pendingArrayParen,
];
$pendingArrayParen = false;
continue;
}
if ($token === '[') {
$stack[] = [
'closing' => ']',
'arrayLike' => true,
];
$pendingArrayParen = false;
continue;
}
if (($token === ')' || $token === ']') && !empty($stack)) {
$top = \array_pop($stack);
if (($top['closing'] ?? null) !== $token) {
$stack = [];
}
}
if ($token !== ',' && $token !== '=>' && $token !== '=') {
$pendingArrayParen = false;
}
}
return $arrayKeyIndexes;
}
/**
* @param array<int, array{closing: string, arrayLike: bool}> $stack
*/
private static function isArrayContext(array $stack): bool
{
if ($stack === []) {
return false;
}
$top = $stack[\count($stack) - 1];
return !empty($top['arrayLike']);
}
/**
* @param array<int, array|string> $tokens
*/
private static function nextSignificantTokenIsDoubleArrow(array $tokens, int $index): bool
{
$count = \count($tokens);
for ($i = $index + 1; $i < $count; $i++) {
$token = $tokens[$i];
if (self::isIgnorableToken($token)) {
continue;
}
return \is_array($token) && $token[0] === \T_DOUBLE_ARROW;
}
return false;
}
/**
* Mark tokens that are very likely to be class names from immediate syntax context.
*
* This is intentionally heuristic and conservative: declaration names,
* extends/implements lists, obvious runtime class references (`new`,
* `instanceof`, `catch`), and static references like `Foo::class`.
*
* On PHP 8+, qualified names arrive as T_NAME_* tokens and can be marked as
* a single unit. On PHP 7.4, qualified names are split across T_STRING and
* T_NS_SEPARATOR tokens, so we stitch them together only in these narrow,
* class-specific contexts.
*
* @param array<int, array|string> $tokens
*
* @return array{0: array<int, true>, 1: array<int, true>}
*/
private static function findClassNameTokenIndexes(array $tokens): array
{
$classIndexes = [];
$keywordIndexes = [];
$context = null;
foreach ($tokens as $index => $token) {
if (self::isIgnorableToken($token)) {
continue;
}
if ($context !== null) {
if (self::literalHighlightType($token) !== null) {
continue;
}
if (self::isClassNameToken($token)) {
if (self::isPseudoClassToken($token)) {
$keywordIndexes[$index] = true;
} else {
$classIndexes[$index] = true;
}
continue;
}
if (self::isNamespaceSeparatorToken($token)) {
$classIndexes[$index] = true;
continue;
}
if ($context === 'list' && ($token === ',' || $token === '&')) {
continue;
}
if ($context === 'union' && ($token === '|' || $token === '&')) {
continue;
}
if ($context === 'union' && $token === '?') {
continue;
}
$context = null;
}
if (!\is_array($token)) {
continue;
}
$tokenType = $token[0];
if (self::isClassDeclarationKeyword($tokenType) && !self::previousSignificantTokenIs($tokens, $index, \T_DOUBLE_COLON)) {
$context = 'single';
continue;
}
if ($tokenType === \T_EXTENDS || $tokenType === \T_IMPLEMENTS) {
$context = 'list';
continue;
}
if (\in_array($tokenType, self::CLASS_REFERENCE_KEYWORDS, true)) {
$context = 'union';
continue;
}
if ($tokenType === \T_DOUBLE_COLON) {
self::markPreviousNameToken($tokens, $classIndexes, $keywordIndexes, $index - 1);
}
}
return [$classIndexes, $keywordIndexes];
}
/**
* @param array|string $token
*/
private static function isIgnorableToken($token): bool
{
return \is_array($token) && ($token[0] === \T_WHITESPACE || $token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT);
}
/**
* @param array|string $token
*/
private static function isClassNameToken($token): bool
{
if (!\is_array($token)) {
return false;
}
if ($token[0] === \T_STRING) {
return true;
}
return \in_array($token[0], self::qualifiedNameTokenTypes(), true);
}
/**
* @param array|string $token
*/
private static function isNamespaceSeparatorToken($token): bool
{
return \is_array($token) && $token[0] === \T_NS_SEPARATOR;
}
/**
* @return int[]
*/
private static function qualifiedNameTokenTypes(): array
{
static $types = null;
if ($types !== null) {
return $types;
}
$types = [];
foreach (['T_NAME_QUALIFIED', 'T_NAME_FULLY_QUALIFIED', 'T_NAME_RELATIVE'] as $tokenName) {
if (\defined($tokenName)) {
$types[] = \constant($tokenName);
}
}
return $types;
}
private static function isClassDeclarationKeyword(int $tokenType): bool
{
if (\in_array($tokenType, [\T_CLASS, \T_INTERFACE, \T_TRAIT], true)) {
return true;
}
return \defined('T_ENUM') && $tokenType === \constant('T_ENUM');
}
/**
* @param array<int, array|string> $tokens
*/
private static function previousSignificantTokenIs(array $tokens, int $index, int $tokenType): bool
{
for ($i = $index - 1; $i >= 0; $i--) {
$token = $tokens[$i];
if (self::isIgnorableToken($token)) {
continue;
}
return \is_array($token) && $token[0] === $tokenType;
}
return false;
}
/**
* @param array<int, array|string> $tokens
* @param array<int, true> $classIndexes
* @param array<int, true> $keywordIndexes
*/
private static function markPreviousNameToken(array $tokens, array &$classIndexes, array &$keywordIndexes, int $index): void
{
$chainIndexes = [];
for ($i = $index; $i >= 0; $i--) {
$token = $tokens[$i];
if (self::isIgnorableToken($token)) {
if ($chainIndexes === []) {
continue;
}
break;
}
if (self::isNamespaceSeparatorToken($token) || self::isClassNameToken($token)) {
$chainIndexes[] = $i;
continue;
}
break;
}
if ($chainIndexes === []) {
return;
}
$firstIndex = $chainIndexes[0];
if (self::literalHighlightType($tokens[$firstIndex]) !== null) {
return;
}
if (self::isPseudoClassToken($tokens[$firstIndex])) {
$keywordIndexes[$firstIndex] = true;
return;
}
foreach ($chainIndexes as $chainIndex) {
$classIndexes[$chainIndex] = true;
}
}
/**
* @param array|string $token
*/
private static function isPseudoClassToken($token): bool
{
return \is_array($token)
&& $token[0] === \T_STRING
&& \in_array(\strtolower($token[1]), ['self', 'parent', 'static'], true);
}
/**
* @param array|string $token
*/
private static function literalHighlightType($token): ?string
{
if (!\is_array($token) || $token[0] !== \T_STRING) {
return null;
}
$value = \strtolower($token[1]);
if ($value === 'true' || $value === 'false') {
return self::HIGHLIGHT_BOOL;
}
if ($value === 'null') {
return self::HIGHLIGHT_CONST;
}
return null;
}
/**
@@ -301,7 +807,7 @@ private static function numberLines(\Generator $lines, ?int $markLine = null): \
$mark = ($markLine === $lineNum) ? self::LINE_MARKER : self::NO_LINE_MARKER;
}
yield \sprintf("%s<aside>%{$pad}s</aside>: %s", $mark, $lineNum, $line);
yield \sprintf("%s<whisper>%{$pad}s:</whisper> %s", $mark, $lineNum, $line);
}
}
@@ -316,4 +822,13 @@ private static function isReflectable(\Reflector $reflector): bool
{
return ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionFunctionAbstract) && \is_file($reflector->getFileName());
}
private static function formatInputSpan(string $spanType, string $spanText, ?OutputFormatterInterface $formatter): string
{
if ($spanType === self::HIGHLIGHT_DEFAULT || $formatter === null || !$formatter->isDecorated() || !$formatter->hasStyle($spanType)) {
return $spanText;
}
return $formatter->getStyle($spanType)->apply($spanText);
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -87,13 +87,7 @@ private static function formatVector(array $vector, array $lines): string
$template = \implode(' ', $template);
return \implode("\n", \array_map(function ($line) use ($template) {
$escaped = \array_map(function ($l) {
if ($l === null) {
return '';
}
return OutputFormatter::escape($l);
}, $line);
$escaped = \array_map(fn ($l) => ($l === null) ? '' : OutputFormatter::escape($l), $line);
return \rtrim(\vsprintf($template, $escaped));
}, $lines));

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -14,6 +14,8 @@
use Psy\Manual\ManualInterface;
use Psy\Reflection\ReflectionConstant;
use Psy\Reflection\ReflectionLanguageConstruct;
use Psy\Reflection\ReflectionMagicMethod;
use Psy\Reflection\ReflectionMagicProperty;
use Psy\Util\Json;
use Symfony\Component\Console\Formatter\OutputFormatter;
@@ -81,9 +83,15 @@ public static function format(\Reflector $reflector): string
case $reflector instanceof \ReflectionClassConstant:
return self::formatClassConstant($reflector);
case $reflector instanceof ReflectionMagicMethod:
return self::formatMagicMethod($reflector);
case $reflector instanceof \ReflectionMethod:
return self::formatMethod($reflector);
case $reflector instanceof ReflectionMagicProperty:
return self::formatMagicProperty($reflector);
case $reflector instanceof \ReflectionProperty:
return self::formatProperty($reflector);
@@ -104,7 +112,7 @@ public static function format(\Reflector $reflector): string
*/
public static function formatName(\Reflector $reflector): string
{
return $reflector->getName();
return self::normalizeName($reflector->getName());
}
/**
@@ -116,9 +124,12 @@ public static function formatName(\Reflector $reflector): string
*/
private static function formatModifiers(\Reflector $reflector): string
{
return \implode(' ', \array_map(function ($modifier) {
return \sprintf('<keyword>%s</keyword>', $modifier);
}, \Reflection::getModifierNames($reflector->getModifiers())));
$modifiers = \array_map(
fn ($modifier) => \sprintf('<keyword>%s</keyword>', $modifier),
\Reflection::getModifierNames($reflector->getModifiers())
);
return \implode(' ', $modifiers);
}
/**
@@ -147,7 +158,7 @@ private static function formatClass(\ReflectionClass $reflector): string
if ($parent = $reflector->getParentClass()) {
$chunks[] = 'extends';
$parentHref = self::getManualHref($parent);
$chunks[] = LinkFormatter::styleWithHref('class', $parent->getName(), $parentHref);
$chunks[] = LinkFormatter::styleWithHref('class', self::normalizeName($parent->getName()), $parentHref);
}
$interfaces = $reflector->getInterfaceNames();
@@ -162,7 +173,7 @@ private static function formatClass(\ReflectionClass $reflector): string
$interfaceHref = null;
}
return LinkFormatter::styleWithHref('class', $name, $interfaceHref);
return LinkFormatter::styleWithHref('class', self::normalizeName($name), $interfaceHref);
}, $interfaces));
}
@@ -295,6 +306,68 @@ private static function formatMethod(\ReflectionMethod $reflector): string
);
}
/**
* Format a magic method signature.
*
* @param ReflectionMagicMethod $reflector
*
* @return string Formatted signature
*/
private static function formatMagicMethod(ReflectionMagicMethod $reflector): string
{
$parts = [];
$parts[] = self::formatModifiers($reflector);
$parts[] = '<keyword>function</keyword>';
$ref = $reflector->returnsReference() ? '&' : '';
$signature = \sprintf(
'%s<function>%s</function>(%s)',
$ref,
$reflector->getName(),
OutputFormatter::escape($reflector->getParameterString())
);
$returnType = $reflector->getDocblockReturnType();
if ($returnType !== null) {
$signature .= \sprintf(': <keyword>%s</keyword>', OutputFormatter::escape($returnType));
}
$parts[] = $signature;
return \implode(' ', \array_filter($parts));
}
/**
* Format a magic property signature.
*
* @param ReflectionMagicProperty $reflector
*
* @return string Formatted signature
*/
private static function formatMagicProperty(ReflectionMagicProperty $reflector): string
{
$parts = [];
$parts[] = self::formatModifiers($reflector);
$type = $reflector->getDocblockType();
if ($type !== null) {
$parts[] = \sprintf('<keyword>%s</keyword>', OutputFormatter::escape($type));
}
$parts[] = \sprintf('<strong>$%s</strong>', $reflector->getName());
if ($reflector->isReadOnly()) {
$parts[] = '<aside>(read-only)</aside>';
} elseif ($reflector->isWriteOnly()) {
$parts[] = '<aside>(write-only)</aside>';
}
return \implode(' ', \array_filter($parts));
}
/**
* Print the function params.
*
@@ -316,7 +389,7 @@ private static function formatFunctionParams(\ReflectionFunctionAbstract $reflec
if ($param->isArray()) {
$hint = '<keyword>array</keyword>';
} elseif ($class = $param->getClass()) {
$hint = LinkFormatter::styleWithHref('class', $class->getName(), self::getManualHref($class));
$hint = LinkFormatter::styleWithHref('class', self::normalizeName($class->getName()), self::getManualHref($class));
}
}
} catch (\Throwable $e) {
@@ -395,7 +468,7 @@ private static function formatReflectionType(?\ReflectionType $type, bool $indic
private static function formatReflectionNamedType(\ReflectionNamedType $type, bool $indicateNullable): string
{
$nullable = $indicateNullable && $type->allowsNull() ? '?' : '';
$typeName = $type->getName();
$typeName = self::normalizeName($type->getName());
if ($type->isBuiltin()) {
return \sprintf('<keyword>%s%s</keyword>', $nullable, OutputFormatter::escape($typeName));
@@ -447,17 +520,17 @@ private static function getManualHref(\Reflector $reflector): ?string
case \ReflectionClass::class:
case \ReflectionObject::class:
case \ReflectionFunction::class:
$query = $reflector->name;
$query = self::normalizeName($reflector->name);
break;
case \ReflectionMethod::class:
$query = $reflector->class.'.'.$reflector->name;
$query = self::normalizeName($reflector->class).'.'.self::normalizeName($reflector->name);
break;
case \ReflectionProperty::class:
case \ReflectionClassConstant::class:
// No simple redirect URLs for properties/constants, link to class instead
$query = $reflector->class;
$query = self::normalizeName($reflector->class);
break;
default:
@@ -484,23 +557,20 @@ private static function getManualDoc(\Reflector $reflector)
case \ReflectionClass::class:
case \ReflectionObject::class:
case \ReflectionFunction::class:
$id = $reflector->name;
$id = self::normalizeName($reflector->name);
break;
case \ReflectionMethod::class:
$id = $reflector->class.'::'.$reflector->name;
case \ReflectionClassConstant::class:
$id = self::normalizeName($reflector->class).'::'.self::normalizeName($reflector->name);
break;
case \ReflectionProperty::class:
$id = $reflector->class.'::$'.$reflector->name;
break;
case \ReflectionClassConstant::class:
$id = $reflector->class.'::'.$reflector->name;
$id = self::normalizeName($reflector->class).'::$'.self::normalizeName($reflector->name);
break;
case ReflectionConstant::class:
$id = $reflector->name;
$id = self::normalizeName($reflector->name);
break;
default:
@@ -509,4 +579,12 @@ private static function getManualDoc(\Reflector $reflector)
return self::$manual->get($id) ?? false;
}
/**
* Strip scoped PHAR namespace prefixes from a name.
*/
private static function normalizeName(string $name): string
{
return (string) \preg_replace('/^_Psy[a-f0-9]+\\\\/i', '', $name);
}
}

View File

@@ -3,7 +3,7 @@
/*
* This file is part of Psy Shell.
*
* (c) 2012-2025 Justin Hileman
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.