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

+72
View File
@@ -184,3 +184,75 @@ class TestPhpLanguageServers:
usage_in_index_php = {"uri_suffix": "index.php", "line": 13, "character": 0}
assert usage_in_index_php in actual_locations_comparable, "Usage of helperFunction in index.php not found"
@pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test that document symbols are properly retrieved after Intelephense capability fix."""
from solidlsp.ls_utils import SymbolUtils
symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "helperFunction"), "helperFunction not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet function not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test that document symbols are properly retrieved for a specific file."""
doc_symbols = language_server.request_document_symbols("helper.php")
all_symbols = doc_symbols.get_all_symbols_and_roots()
symbol_names = [sym.get("name") for sym in all_symbols[0] if sym.get("name")]
assert "helperFunction" in symbol_names, f"helperFunction not found in document symbols. Found: {symbol_names}"
@pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
def test_document_symbols_hierarchical_structure(self, language_server: SolidLanguageServer) -> None:
"""Verify Intelephense returns hierarchical DocumentSymbol format.
When hierarchicalDocumentSymbolSupport is declared in client capabilities,
Intelephense returns DocumentSymbol[] where class methods appear as children
of their parent class. Without this declaration, it falls back to a flat
SymbolInformation[] list where all symbols appear at root level with no
parent-child relationships.
"""
all_symbols, root_symbols = language_server.request_document_symbols("sample.php").get_all_symbols_and_roots()
root_names = [s.get("name") for s in root_symbols]
assert "Animal" in root_names, f"Animal class not found at root level. Roots: {root_names}"
assert "Dog" in root_names, f"Dog class not found at root level. Roots: {root_names}"
assert "Cat" in root_names, f"Cat class not found at root level. Roots: {root_names}"
# Verify Dog has method children — this is the key assertion for hierarchical support.
# With a flat response, Dog would have no children and all methods would be at root level.
dog_symbol = next((s for s in root_symbols if s.get("name") == "Dog"), None)
assert dog_symbol is not None, "Dog class not found in root symbols"
dog_children = dog_symbol.get("children", [])
dog_child_names = [c.get("name") for c in dog_children]
assert (
len(dog_child_names) > 0
), f"Dog class has no children — hierarchicalDocumentSymbolSupport is not working. All root symbols: {root_names}"
expected_methods = {"greet", "fetch", "getBreed", "describe"}
missing = expected_methods - set(dog_child_names)
assert not missing, f"Dog class missing expected methods: {missing}. Children found: {dog_child_names}"
# Methods must NOT appear at root level (that would indicate the flat fallback format).
assert "greet" not in root_names, f"greet should be a child of Dog, not at root level. Roots: {root_names}"
assert "fetch" not in root_names, f"fetch should be a child of Dog, not at root level. Roots: {root_names}"
@pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
def test_full_symbol_tree_within_file(self, language_server: SolidLanguageServer) -> None:
"""Verify request_full_symbol_tree scoped to a PHP file returns correct symbols.
This validates that Intelephense responds correctly when symbols are requested
for a single file, including class/method hierarchy in sample.php.
"""
from solidlsp.ls_utils import SymbolUtils
symbols = language_server.request_full_symbol_tree(within_relative_path="sample.php")
assert SymbolUtils.symbol_tree_contains_name(symbols, "Dog"), "Dog not found in sample.php symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Animal"), "Animal not found in sample.php symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet method not found in sample.php symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "fetch"), "fetch method not found in sample.php symbol tree"
# Methods must appear as children of Dog, not as root-level symbols
dog_root = next((s for s in symbols if s.get("name") == "Dog"), None)
if dog_root is not None:
assert SymbolUtils.symbol_tree_contains_name([dog_root], "greet"), "greet should be nested under Dog in symbol tree"