Fix PHP symbol retrieval: declare missing Intelephense client capabilities (#1099)

Fix PHP symbol retrieval via Intelephense LSP

* intelephense.py: Add missing client capability declarations for
  documentSymbol (with hierarchical support), references, hover, and
  workspace symbol. Without these, Intelephense does not advertise
  documentSymbolProvider and returns empty results for get_symbols_overview.

Add PHP tests validating hierarchical symbols and LS routing fix

- test_document_symbols_hierarchical_structure: verifies Intelephense
  returns DocumentSymbol[] with class/method hierarchy (not flat
  SymbolInformation[]) when hierarchicalDocumentSymbolSupport is declared

- test_full_symbol_tree_within_file: verifies request_full_symbol_tree
  scoped to sample.php returns Dog/Animal/greet/fetch with Dog containing
  greet as a child (not at root level)

- test_find_symbol_within_php_file: verifies find_symbol with a PHP
  file path (relative_path=) routes to the PHP language server via
  get_language_server() rather than iterating all servers

Peripheral changes:

- symbol.py (LanguageServerSymbolRetriever.find_symbols): When searching
  within a specific file, route to the file-type-appropriate language server
  via get_language_server() instead of querying all servers. This ensures
  PHP files are served by Intelephense and not silently skipped.
This commit is contained in:
Terrance Orletsky authored and Dr. Dominik Jain committed 2026-03-12 15:19:11 +01:00
1 parent b3a6045f7e
commit 18cee10fa9
5 files changed
+246 -6

No files matched your search

@@ -0,0 +1,122 @@
<?php
namespace SerenaTest;
/**
* Interface for objects that can greet.
*/
interface GreeterInterface
{
public function greet(string $name): string;
}
/**
* Abstract base class for animals.
*/
abstract class Animal
{
protected string $name;
protected int $age;
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
public function getName(): string
{
return $this->name;
}
public function getAge(): int
{
return $this->age;
}
abstract public function describe(): string;
}
/**
* A concrete animal that can greet visitors.
*/
class Dog extends Animal implements GreeterInterface
{
private string $breed;
public function __construct(string $name, int $age, string $breed)
{
parent::__construct($name, $age);
$this->breed = $breed;
}
public function greet(string $visitorName): string
{
return "Woof! I'm {$this->name}. Hello, {$visitorName}!";
}
public function getBreed(): string
{
return $this->breed;
}
public function describe(): string
{
return "Dog: {$this->name} ({$this->breed}), age {$this->age}";
}
public function fetch(string $item): string
{
return "{$this->name} fetches the {$item}!";
}
}
/**
* Another concrete animal.
*/
class Cat extends Animal
{
private bool $indoor;
public function __construct(string $name, int $age, bool $indoor = true)
{
parent::__construct($name, $age);
$this->indoor = $indoor;
}
public function isIndoor(): bool
{
return $this->indoor;
}
public function describe(): string
{
$type = $this->indoor ? 'indoor' : 'outdoor';
return "Cat: {$this->name} ({$type}), age {$this->age}";
}
}
const MAX_ANIMALS = 100;
const DEFAULT_BREED = 'Mixed';
/**
* Factory function to create an animal by type name.
*/
function createAnimal(string $type, string $name, int $age): Animal
{
return match ($type) {
'dog' => new Dog($name, $age, DEFAULT_BREED),
'cat' => new Cat($name, $age),
default => throw new \InvalidArgumentException("Unknown animal type: {$type}"),
};
}
/**
* Returns a summary string for a list of animals.
*
* @param Animal[] $animals
*/
function summarizeAnimals(array $animals): string
{
return implode(', ', array_map(fn(Animal $a) => $a->describe(), $animals));
}