mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 12:29:04 +00:00
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:
1 parent
281e9db2eb
commit
3c8e9854f6
7 files changed
+40
-64
No files matched your search
+4
-4
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user