Improve identification of container/low-level symbols (#1834)

This affects results returned for request_containing_symbol,
some tests were adjusted accordingly.
This commit is contained in:
Dr. Dominik Jain authored and GitHub committed 2026-08-09 20:12:51 +02:00
1 parent 281e9db2eb
commit 3c8e9854f6
7 files changed
+40 -64

No files matched your search

+4 -4
View File
@@ -97,10 +97,10 @@ Status of the `main` branch. Changes prior to the next official version change w
file extensions, so it is not auto-detected and must be selected explicitly; requires the
`deno` CLI on PATH
- Fix: `find_referencing_symbols` reported file-level containers for references located inside Go
struct bodies, interface bodies and `const` groups, because containing-symbol detection admitted
only Python's container kinds; `Struct`, `Interface` and `Constant` symbols are now admitted as
containers
struct bodies, interface bodies and `const` groups; improve the logic for finding the nearest
enclosing symbol, adding the helper function `SymbolKind.is_container` (which is now also
applied to identify high-level symbols that should appear in symbol overiews).
* JetBrains:
- `jet_brains_find_symbol`: Disallow wildcard-only search, delegating to overview tool if request is for file
+1 -1
View File
@@ -247,7 +247,7 @@ class LanguageServerSymbol(Symbol, ToStringMixin):
:return: whether the symbol is a low-level symbol (variable, constant, etc.), which typically represents data
rather than structure and therefore is not relevant in a high-level overview of the code.
"""
return self.symbol_kind >= SymbolKind.Variable.value
return not SymbolKind.is_container(self.symbol_kind)
@property
def overload_idx(self) -> int | None:
+8 -19
View File
@@ -48,6 +48,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import (
LocationLink,
RenameParams,
SymbolInformation,
SymbolKind,
)
from solidlsp.lsp_protocol_handler.server import (
LSPError,
@@ -2590,16 +2591,6 @@ class SolidLanguageServer(ABC):
location["relativePath"] = relative_file_path
location["uri"] = Path(absolute_file_path).as_uri()
# allowed container kinds; Struct and Interface cover languages (e.g. Go, Rust, C) whose
# containers are not reported as classes
container_symbol_kinds = {
ls_types.SymbolKind.Method,
ls_types.SymbolKind.Function,
ls_types.SymbolKind.Class,
ls_types.SymbolKind.Struct,
ls_types.SymbolKind.Interface,
}
def is_position_in_range(line: int, range_d: ls_types.Range) -> bool:
start = range_d["start"]
end = range_d["end"]
@@ -2615,17 +2606,15 @@ class SolidLanguageServer(ABC):
column_condition = column >= start["character"]
return line_condition and column_condition
def is_multiline_container(s):
return SymbolKind.is_container(s["kind"]) and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"]
def is_variable_or_constant(s):
return s["kind"] in {ls_types.SymbolKind.Variable, ls_types.SymbolKind.Constant}
# Only consider containers that are not one-liners (otherwise we may get imports)
candidate_containers = [
s
for s in document_symbols.iter_symbols()
if s["kind"] in container_symbol_kinds and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"]
]
# variables and constants are admitted even as one-liners (e.g. members of a Go const group)
var_containers = [
s for s in document_symbols.iter_symbols() if s["kind"] in {ls_types.SymbolKind.Variable, ls_types.SymbolKind.Constant}
]
candidate_containers.extend(var_containers)
candidate_containers = [s for s in document_symbols.iter_symbols() if is_multiline_container(s) or is_variable_or_constant(s)]
if not candidate_containers:
return None
@@ -182,6 +182,24 @@ class SymbolKind(IntEnum):
def _missing_(cls, value: Any) -> "SymbolKind":
return cls.Unknown
@staticmethod
def is_container(kind: "SymbolKind") -> bool:
"""Returns True if the symbol kind is a container (i.e. can have children)."""
return kind in {
SymbolKind.File,
SymbolKind.Module,
SymbolKind.Namespace,
SymbolKind.Package,
SymbolKind.Class,
SymbolKind.Method,
SymbolKind.Constructor,
SymbolKind.Enum,
SymbolKind.Interface,
SymbolKind.Function,
SymbolKind.Object,
SymbolKind.Struct,
}
class SymbolTag(IntEnum):
"""Symbol tags are extra annotations that tweak the rendering of a symbol.
@@ -24,17 +24,13 @@ class TestTomlSymbolRetrieval:
@pytest.mark.parametrize("language_server", [LanguageServerId.TOML], indirect=True)
@pytest.mark.parametrize("repo_path", [LanguageServerId.TOML], indirect=True)
def test_request_containing_symbol_behavior(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test request_containing_symbol behavior for TOML files.
Note: Taplo LSP doesn't support definition/containing symbol lookups for TOML files
since TOML is a configuration format, not code. This test verifies the behavior.
"""
Test request_containing_symbol behavior for TOML files.
"""
# Line 2 (0-indexed: 1) is inside the [package] table
containing_symbol = language_server.request_containing_symbol("Cargo.toml", 1, 5)
# Taplo doesn't support containing symbol lookup - returns None
# This is expected behavior for a configuration file format
assert containing_symbol is None, "TOML LSP doesn't support containing symbol lookup"
assert "[package]" in containing_symbol["body"].get_text()
@pytest.mark.parametrize("language_server", [LanguageServerId.TOML], indirect=True)
@pytest.mark.parametrize("repo_path", [LanguageServerId.TOML], indirect=True)
-28
View File
@@ -36,20 +36,6 @@ class TestVueInvalidPositions:
assert result is None or result == {}, f"Negative line number should return None or empty dict, got: {result}"
@pytest.mark.parametrize("language_server", [LanguageServerId.VUE], indirect=True)
def test_negative_character_number(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol with negative character number.
Expected behavior: Should return None or empty dict, not crash.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request containing symbol at invalid negative character
result = language_server.request_containing_symbol(file_path, 10, -1)
# Should handle gracefully - return None or empty dict
assert result is None or result == {}, f"Negative character number should return None or empty dict, got: {result}"
@pytest.mark.parametrize("language_server", [LanguageServerId.VUE], indirect=True)
def test_line_number_beyond_file_length(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol beyond file length.
@@ -67,20 +53,6 @@ class TestVueInvalidPositions:
# Verify it's an index error for list access
assert "list index out of range" in str(exc_info.value), f"Expected 'list index out of range' error, got: {exc_info.value}"
@pytest.mark.parametrize("language_server", [LanguageServerId.VUE], indirect=True)
def test_character_number_beyond_line_length(self, language_server: SolidLanguageServer) -> None:
"""Test requesting containing symbol beyond line length.
Expected behavior: Should return None or empty dict, not crash.
"""
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Request containing symbol at character 99999 (way beyond line length)
result = language_server.request_containing_symbol(file_path, 10, 99999)
# Should handle gracefully - return None or empty dict
assert result is None or result == {}, f"Character beyond line length should return None or empty dict, got: {result}"
@pytest.mark.parametrize("language_server", [LanguageServerId.VUE], indirect=True)
def test_references_at_negative_line(self, language_server: SolidLanguageServer) -> None:
"""Test requesting references with negative line number."""
@@ -2,6 +2,7 @@ import os
import pytest
from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind
@@ -81,7 +82,7 @@ class TestVueSymbolRetrieval:
], f"Expected property/variable/function kind for computed, got {containing_symbol.get('kind')}"
@pytest.mark.parametrize("language_server", [LanguageServerId.VUE], indirect=True)
def test_request_containing_symbol_no_containing_symbol(self, language_server: SolidLanguageServer) -> None:
def test_request_containing_symbol_import_in_script_tag(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("src", "components", "CalculatorInput.vue")
# Position in the import statements at the top of the script setup
@@ -92,10 +93,10 @@ class TestVueSymbolRetrieval:
# Request containing symbol for a position in the imports
containing_symbol = language_server.request_containing_symbol(file_path, import_line, import_character)
# Should return None or empty dictionary for positions without containing symbol
assert containing_symbol is None or containing_symbol == {}, (
f"Expected None or empty dict for import position, got {containing_symbol}"
)
# Containing symbol is the script tag
assert containing_symbol is not None
s = LanguageServerSymbol(containing_symbol)
assert s.get_name_path().startswith("script")
@pytest.mark.parametrize("language_server", [LanguageServerId.VUE], indirect=True)
def test_request_referencing_symbols_store_function(self, language_server: SolidLanguageServer) -> None: