
| Current Path : /var/www/html/dinarosun/vendor/psy/psysh/src/Completion/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/html/dinarosun/vendor/psy/psysh/src/Completion/ContextAnalyzer.php |
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Completion;
use PhpParser\Error as PhpParserError;
use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\NullsafeMethodCall;
use PhpParser\Node\Expr\NullsafePropertyFetch;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\NodeTraverser;
use PhpParser\Parser;
use PhpParser\PrettyPrinter\Standard as Printer;
use Psy\CodeCleaner;
use Psy\ParserFactory;
/**
* Analyzes input to determine completion context.
*
* This stage provides the parser-derived starting point for completion. Its
* job is to describe the PHP syntax at the cursor, not to decide every higher-
* level completion mode built on top of that syntax.
*/
class ContextAnalyzer
{
private Parser $parser;
private Printer $printer;
private ?CodeCleaner $cleaner;
public function __construct(?CodeCleaner $cleaner = null)
{
$this->parser = (new ParserFactory())->createParser();
$this->printer = new Printer();
$this->cleaner = $cleaner;
}
/**
* Analyze input and return the coarse parser-derived context.
*/
public function analyze(string $input, int $cursor, array $readlineInfo = []): AnalysisResult
{
// Cursor is in code-point units, so use mb_substr
$inputToCursor = \mb_substr($input, 0, $cursor);
$cursorAtEnd = ($cursor >= \mb_strlen($input));
$analysis = $this->tryParse($inputToCursor, $cursorAtEnd);
$parseSucceeded = $analysis !== null;
$analysis = $analysis ?? new AnalysisResult(CompletionKind::UNKNOWN, '');
$analysis->parseSucceeded = $parseSucceeded;
$analysis->input = $inputToCursor;
$analysis->tokens = @\token_get_all('<?php '.$inputToCursor);
$analysis->readlineInfo = $readlineInfo;
return $analysis;
}
/**
* Try to parse input and analyze the resulting AST.
*/
private function tryParse(string $input, bool $cursorAtEnd): ?AnalysisResult
{
$code = '<?php '.$input;
// Try with semicolon first (most common case), then without
try {
$stmts = $this->parser->parse($code.';');
} catch (PhpParserError $e) {
try {
$stmts = $this->parser->parse($code);
} catch (PhpParserError $e2) {
return null;
}
}
if (empty($stmts)) {
return new AnalysisResult(CompletionKind::UNKNOWN, '');
}
$visitor = new DeepestNodeVisitor();
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse($stmts);
$node = $visitor->getDeepestNode();
if ($node === null) {
return new AnalysisResult(CompletionKind::UNKNOWN, '');
}
// Trailing whitespace means the user has moved past this token
if ($cursorAtEnd && $input !== \rtrim($input)) {
return new AnalysisResult(CompletionKind::UNKNOWN, '');
}
return $this->analyzeNode($node);
}
/**
* Analyze a specific AST node to determine completion context.
*/
private function analyzeNode(Node $node): AnalysisResult
{
// $foo->bar, $foo->bar(), $foo?->bar, $foo?->bar()
if (
$node instanceof MethodCall
|| $node instanceof PropertyFetch
|| $node instanceof NullsafeMethodCall
|| $node instanceof NullsafePropertyFetch
) {
$leftSide = $this->extractExpression($node->var);
$prefix = $node->name instanceof Identifier ? $node->name->name : '';
$result = new AnalysisResult(CompletionKind::OBJECT_MEMBER, $prefix, $leftSide);
$result->leftSideNode = $node->var;
return $result;
}
// Foo::bar(), Foo::BAR, Foo::$bar
if (
$node instanceof StaticCall
|| $node instanceof ClassConstFetch
|| $node instanceof StaticPropertyFetch
) {
$leftSide = $this->extractExpression($node->class);
$prefix = $node->name instanceof Identifier ? $node->name->name : '';
$result = new AnalysisResult(CompletionKind::STATIC_MEMBER, $prefix, $leftSide);
$result->leftSideNode = $node->class;
return $result;
}
// new Foo
if ($node instanceof New_) {
$prefix = $node->class instanceof Name ? $node->class->toString() : '';
return new AnalysisResult(CompletionKind::CLASS_NAME, $prefix);
}
// $foo
if ($node instanceof Variable) {
$prefix = \is_string($node->name) ? $node->name : '';
return new AnalysisResult(CompletionKind::VARIABLE, $prefix);
}
// foo()
if ($node instanceof FuncCall && $node->name instanceof Name) {
return new AnalysisResult(CompletionKind::FUNCTION_NAME, $node->name->toString());
}
// FOO (could be a constant, function, or class reference)
if ($node instanceof ConstFetch && $node->name instanceof Name) {
return new AnalysisResult(CompletionKind::SYMBOL, $node->name->toString());
}
if ($node instanceof Identifier) {
return new AnalysisResult(CompletionKind::UNKNOWN, $node->name);
}
// Bare name: foo or Foo\Bar
if ($node instanceof Name) {
return new AnalysisResult(CompletionKind::SYMBOL, $node->toString());
}
return new AnalysisResult(CompletionKind::UNKNOWN, '');
}
/**
* Extract a string representation of an expression.
*
* Uses the printer to convert the AST node back to code.
*/
private function extractExpression($expr): string
{
if ($expr instanceof Variable && \is_string($expr->name)) {
return '$'.$expr->name;
}
if ($expr instanceof Name) {
return $this->resolveClassName($expr);
}
if ($expr instanceof Node\Expr) {
return $this->printer->prettyPrintExpr($expr);
}
return '';
}
/**
* Resolve a class name using CodeCleaner's namespace context.
*
* This ensures we use the same use statements and namespace as the code
* being executed.
*/
private function resolveClassName(Name $name): string
{
if ($this->cleaner === null) {
return $name->toString();
}
return $this->cleaner->resolveClassName($name->toString());
}
}