diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba64b04..056390fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ Status of the `main` branch. Changes prior to the next official version change w at the Yarn-generated SDK. - `SvelteLanguageServer`: Fix diagnostics requests for TypeScript/JavaScript files incorrectly being processed by the Svelte LS instead of the TypeScript LS. + - `SvelteLanguageServer`: Fix document-symbol requests for TypeScript/JavaScript files returning empty + results in svelte-only mode (`languages: [svelte]`). They are now routed to the companion TS server, + so symbols defined in plain `.ts`/`.js` files are again discoverable via `find_symbol`/ + `get_symbols_overview`, and `find_referencing_symbols` no longer fails to locate `.ts`/`.js` symbols. #1552 - Improve quoting of arguments in shell executions * JetBrains: diff --git a/src/solidlsp/language_servers/svelte_language_server.py b/src/solidlsp/language_servers/svelte_language_server.py index c633d10f..a0cff5b1 100644 --- a/src/solidlsp/language_servers/svelte_language_server.py +++ b/src/solidlsp/language_servers/svelte_language_server.py @@ -21,8 +21,10 @@ from solidlsp.language_servers.common import ( ) from solidlsp.language_servers.typescript_language_server import TypeScriptLanguageServer from solidlsp.ls import ( + DocumentSymbols, LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, + LSPFileBuffer, SolidLanguageServer, ) from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig @@ -647,6 +649,25 @@ class SvelteLanguageServer(SolidLanguageServer): return self._ts_server.request_definition(relative_file_path, line, column) return super().request_definition(relative_file_path, line, column) + @override + def request_document_symbols(self, relative_file_path: str, file_buffer: LSPFileBuffer | None = None) -> DocumentSymbols: + """Delegate document-symbol requests for .ts/.js files to the companion TS server. + + The base svelte LS only provides ``documentSymbol`` for .svelte files and returns nothing for + .ts/.js files. Without this override, symbols defined in plain .ts/.js modules would be + undiscoverable via ``find_symbol``/``get_symbols_overview`` and ``find_referencing_symbols`` + would fail outright, since it must first locate the target symbol via ``documentSymbol``. + Routing .ts/.js files to the companion (svelte-plugin-aware) typescript LS mirrors the + existing references/definition/rename delegations. + + The companion's own ``file_buffer`` is not reused here: the provided buffer (if any) is bound + to this svelte LS, so the companion opens and reads the file itself. + Falls back to the svelte LS when the companion is unavailable or when the file is .svelte. + """ + if _is_ts_file(relative_file_path) and self._ts_server is not None: + return self._ts_server.request_document_symbols(relative_file_path) + return super().request_document_symbols(relative_file_path, file_buffer) + @override def request_text_document_diagnostics( self, diff --git a/test/solidlsp/svelte/test_svelte_basic.py b/test/solidlsp/svelte/test_svelte_basic.py index 6540d1b8..e49ebd0b 100644 --- a/test/solidlsp/svelte/test_svelte_basic.py +++ b/test/solidlsp/svelte/test_svelte_basic.py @@ -30,6 +30,12 @@ class TestSvelteLanguageServer: assert SymbolUtils.symbol_tree_contains_name(symbols, "Game"), "Game class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "words"), "words export not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "count"), "count export not found in symbol tree" + # GAME_VERSION is defined *only* in a .ts file (game.ts); it is merely imported elsewhere. + # It therefore guards against the regression where .ts document symbols are not routed to the + # companion TS server (unlike game/Game/words/count, which also appear in .svelte files). + assert SymbolUtils.symbol_tree_contains_name(symbols, "GAME_VERSION"), ( + "GAME_VERSION (defined only in a .ts file) not found in symbol tree" + ) @pytest.mark.parametrize("language_server", [Language.SVELTE], indirect=True) def test_document_symbols_inside_svelte_file(self, language_server: SolidLanguageServer) -> None: @@ -40,6 +46,30 @@ class TestSvelteLanguageServer: assert "offset" in symbol_names assert "modulo" in symbol_names + @pytest.mark.parametrize("language_server", [Language.SVELTE], indirect=True) + def test_document_symbols_inside_typescript_file(self, language_server: SolidLanguageServer) -> None: + # document symbols of a plain .ts file must be served by the companion TS server, not the + # base svelte LS (which only provides documentSymbol for .svelte files); see issue #1552. + file_path = os.path.join("src", "lib", "game.ts") + symbol_names = {symbol["name"] for symbol in language_server.request_document_symbols(file_path).iter_symbols()} + + # top-level symbols + assert "GAME_VERSION" in symbol_names, symbol_names + assert "Game" in symbol_names, symbol_names + # members nested inside the Game class + assert "enter" in symbol_names, symbol_names + assert "toString" in symbol_names, symbol_names + + @pytest.mark.parametrize("language_server", [Language.SVELTE], indirect=True) + def test_overview_of_typescript_file(self, language_server: SolidLanguageServer) -> None: + # get_symbols_overview on a .ts file must not be empty in svelte-only mode (issue #1552). + file_path = os.path.join("src", "lib", "game.ts") + overview = language_server.request_overview(file_path) + + top_level_names = {symbol["name"] for symbols in overview.values() for symbol in symbols} + assert "GAME_VERSION" in top_level_names, overview + assert "Game" in top_level_names, overview + @pytest.mark.parametrize("language_server", [Language.SVELTE], indirect=True) def test_definition_from_component_import_to_svelte_file(self, language_server: SolidLanguageServer) -> None: file_path = os.path.join("src", "lib", "components", "Header.svelte") diff --git a/test/solidlsp/svelte/test_svelte_symbols.py b/test/solidlsp/svelte/test_svelte_symbols.py new file mode 100644 index 00000000..c6171d4b --- /dev/null +++ b/test/solidlsp/svelte/test_svelte_symbols.py @@ -0,0 +1,67 @@ +"""Agent-level symbol discovery/navigation for plain .ts files of a svelte-only project. + +Reproduces https://github.com/oraios/serena/issues/1552: in ``languages: [svelte]`` mode, +document-symbol requests for .ts/.js files were answered by the base svelte LS (which only +serves .svelte files) instead of being routed to the companion, svelte-plugin-aware TS server. +As a consequence, symbols defined in plain .ts files were undiscoverable through ``find_symbol``/ +``get_symbols_overview``, and ``find_referencing_symbols`` failed with ``No symbol matching ...`` +because it must first locate the target symbol via ``documentSymbol``. + +These tests exercise the high-level :class:`LanguageServerSymbolRetriever` that backs those agent +tools, complementing the lower-level checks in ``test_svelte_basic.py``. +""" + +import os + +import pytest + +from serena.project import Project +from serena.symbol import LanguageServerSymbolRetriever +from solidlsp.ls_config import Language +from solidlsp.ls_types import SymbolKind + +pytestmark = pytest.mark.svelte + +# a plain .ts module whose exported symbols are consumed by both .ts and .svelte files +GAME_TS = os.path.join("src", "lib", "game.ts") + + +class TestSvelteTypeScriptSymbolDiscovery: + @pytest.mark.parametrize("project_with_ls", [Language.SVELTE], indirect=True) + def test_find_symbol_in_typescript_file(self, project_with_ls: Project) -> None: + retriever = LanguageServerSymbolRetriever(project_with_ls) + + # GAME_VERSION is *defined* only in game.ts (merely imported elsewhere) + version_symbols = retriever.find("GAME_VERSION", within_relative_path=GAME_TS) + assert [s.name for s in version_symbols] == ["GAME_VERSION"] + assert version_symbols[0].relative_path is not None + assert version_symbols[0].relative_path.replace("\\", "/") == "src/lib/game.ts" + + # the Game class is discoverable and correctly typed + game_symbols = retriever.find("Game", within_relative_path=GAME_TS, include_kinds=[SymbolKind.Class]) + assert len(game_symbols) == 1, game_symbols + assert game_symbols[0].symbol_kind == SymbolKind.Class + + # nested members of the .ts class are discoverable via their name path + enter_symbols = retriever.find("Game/enter", within_relative_path=GAME_TS) + assert len(enter_symbols) == 1, enter_symbols + assert enter_symbols[0].get_name_path() == "Game/enter" + + @pytest.mark.parametrize("project_with_ls", [Language.SVELTE], indirect=True) + def test_find_referencing_symbols_locates_typescript_symbol(self, project_with_ls: Project) -> None: + retriever = LanguageServerSymbolRetriever(project_with_ls) + + # The core of issue #1552 for find-references: the target symbol must first be located via + # documentSymbol. Before the fix, .ts document symbols were empty, so locating the symbol + # raised ValueError("No symbol matching 'GAME_VERSION' found") *before the companion TS + # server was ever consulted*. Verify the symbol is now uniquely discoverable. + symbol = retriever.find_unique("GAME_VERSION", within_relative_path=GAME_TS) + assert symbol.name == "GAME_VERSION" + assert symbol.relative_path is not None + assert symbol.relative_path.replace("\\", "/") == "src/lib/game.ts" + + # The end-to-end find_referencing_symbols call must now get past symbol discovery without + # raising. (The cross-file/.svelte reference *content* is covered by test_svelte_references.py + # and depends on the companion TS server's reference graph rather than on this fix.) + references = retriever.find_referencing_symbols("GAME_VERSION", GAME_TS) + assert isinstance(references, list)