mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 10:16:00 +00:00
Merge pull request #105 from oraios/tests/test_ls_on_rust
Fixes for Go, tests for TS and Go
This commit is contained in:
15 files changed
+215
-138
No files matched your search
@@ -16,8 +16,14 @@ jobs:
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
|
||||
# Include the appropriate package manager steps based on the selection
|
||||
|
||||
# Add Go bin directory to PATH for this workflow
|
||||
# GITHUB_PATH is a special file that GitHub Actions uses to modify PATH
|
||||
# Writing to this file adds the directory to the PATH for subsequent steps
|
||||
- name: Install gopls
|
||||
run: |
|
||||
go install golang.org/x/tools/gopls@latest
|
||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Create virtual environment
|
||||
|
||||
Vendored
+3
-1
@@ -10,5 +10,7 @@
|
||||
"pixi",
|
||||
"sensai",
|
||||
"vibing"
|
||||
]
|
||||
],
|
||||
"basedpyright.disableLanguageServices": true,
|
||||
"python.languageServer": "None"
|
||||
}
|
||||
@@ -32,6 +32,7 @@ from .lsp_protocol_handler import lsp_types as LSPTypes
|
||||
from .lsp_protocol_handler.lsp_constants import LSPConstants
|
||||
from .lsp_protocol_handler.lsp_types import SymbolKind
|
||||
from .lsp_protocol_handler.server import (
|
||||
Error,
|
||||
LanguageServerHandler,
|
||||
ProcessLaunchInfo,
|
||||
)
|
||||
@@ -614,7 +615,6 @@ class LanguageServer:
|
||||
)
|
||||
except Exception as e:
|
||||
# Catch LSP internal error (-32603) and raise a more informative exception
|
||||
from multilspy.lsp_protocol_handler.server import Error
|
||||
if isinstance(e, Error) and getattr(e, 'code', None) == -32603:
|
||||
raise RuntimeError(
|
||||
f"LSP internal error (-32603) when requesting references for {relative_file_path}:{line}:{column}. "
|
||||
|
||||
@@ -5,13 +5,15 @@ import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncIterator
|
||||
from typing import AsyncIterator, List
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy import multilspy_types
|
||||
from multilspy.multilspy_exceptions import MultilspyException
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.language_server import LanguageServer
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.lsp_protocol_handler.server import Error, ProcessLaunchInfo
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
|
||||
@@ -57,28 +59,21 @@ class Gopls(LanguageServer):
|
||||
Check if required Go runtime dependencies are available.
|
||||
Raises RuntimeError with helpful message if dependencies are missing.
|
||||
"""
|
||||
missing_deps = []
|
||||
|
||||
# Check for Go installation
|
||||
go_version = cls._get_go_version()
|
||||
if not go_version:
|
||||
missing_deps.append(("Go", "https://golang.org/doc/install"))
|
||||
raise RuntimeError("Go is not installed. Please install Go from https://golang.org/doc/install and make sure it is added to your PATH.")
|
||||
|
||||
# Check for gopls
|
||||
gopls_version = cls._get_gopls_version()
|
||||
if not gopls_version:
|
||||
missing_deps.append(("gopls", "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme"))
|
||||
|
||||
if missing_deps:
|
||||
error_msg = "Missing required dependencies:\n"
|
||||
for dep, install_url in missing_deps:
|
||||
error_msg += f"- {dep}: Please install from {install_url}\n"
|
||||
raise RuntimeError(error_msg)
|
||||
raise RuntimeError(
|
||||
"Found a Go version but gopls is not installed.\n"
|
||||
"Please install gopls as described in https://pkg.go.dev/golang.org/x/tools/gopls#section-readme\n\n"
|
||||
"After installation, make sure it is added to your PATH (it might be installed in a different location than Go)."
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
# Check runtime dependencies before initializing
|
||||
self.setup_runtime_dependency()
|
||||
|
||||
super().__init__(
|
||||
@@ -159,3 +154,20 @@ class Gopls(LanguageServer):
|
||||
|
||||
await self.server.shutdown()
|
||||
await self.server.stop()
|
||||
|
||||
@override
|
||||
async def request_references(
|
||||
self, relative_file_path: str, line: int, column: int
|
||||
) -> List[multilspy_types.Location]:
|
||||
NUM_COLS_TO_TRY = 10
|
||||
"""selectionRange in Gopls always return the wrong column,
|
||||
the one at the beginnning of the declaration instead of the symbol. We do a dirty hack here.
|
||||
See https://github.com/golang/go/issues/73521
|
||||
"""
|
||||
actual_column = column
|
||||
for actual_column in range(column, column+NUM_COLS_TO_TRY):
|
||||
try:
|
||||
return await super().request_references(relative_file_path, line, actual_column)
|
||||
except (Error, RuntimeError, MultilspyException) as e:
|
||||
self.logger.log(f"Cannot find symbol at {line=}, column={actual_column}, trying to bump column by 1", logging.INFO)
|
||||
raise RuntimeError(f"Failed to find references for symbol at {line=}, {column=}")
|
||||
@@ -38,6 +38,7 @@ from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from .lsp_requests import LspNotification, LspRequest
|
||||
from .lsp_types import ErrorCodes
|
||||
from ..multilspy_exceptions import MultilspyException
|
||||
|
||||
StringDict = Dict[str, Any]
|
||||
PayloadLike = Union[List[StringDict], StringDict, None]
|
||||
@@ -481,7 +482,7 @@ class LanguageServerHandler:
|
||||
await self._send_payload(make_request(method, request_id, params))
|
||||
await request.cv.wait()
|
||||
if isinstance(request.error, Error):
|
||||
raise request.error
|
||||
raise MultilspyException(f"Could not process request {method} with params:\n{params}.\n Language server error: {request.error}") from request.error
|
||||
return request.result
|
||||
|
||||
def _send_payload_sync(self, payload: StringDict) -> None:
|
||||
|
||||
@@ -17,6 +17,7 @@ from enum import Enum
|
||||
from multilspy.multilspy_exceptions import MultilspyException
|
||||
from pathlib import PurePath, Path
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_types import UnifiedSymbolInformation
|
||||
|
||||
|
||||
class TextUtils:
|
||||
@@ -322,3 +323,13 @@ class PlatformUtils:
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
raise MultilspyException("dotnet or mono not found on the system")
|
||||
|
||||
|
||||
class SymbolUtils:
|
||||
@staticmethod
|
||||
def symbol_tree_contains_name(roots: list[UnifiedSymbolInformation], name: str) -> bool:
|
||||
for symbol in roots:
|
||||
if symbol["name"] == name:
|
||||
return True
|
||||
if SymbolUtils.symbol_tree_contains_name(symbol["children"], name):
|
||||
return True
|
||||
return False
|
||||
+2
-2
@@ -59,7 +59,7 @@ def language_server(request: LanguageParamRequest):
|
||||
Example:
|
||||
```
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_python_server(language_server):
|
||||
def test_python_server(language_server: SyncLanguageServer) -> None:
|
||||
# Use the Python language server
|
||||
pass
|
||||
```
|
||||
@@ -67,7 +67,7 @@ def language_server(request: LanguageParamRequest):
|
||||
You can also test multiple languages in a single test:
|
||||
```
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON, Language.TYPESCRIPT], indirect=True)
|
||||
def test_multiple_languages(language_server):
|
||||
def test_multiple_languages(language_server: SyncLanguageServer) -> None:
|
||||
# This test will run once for each language
|
||||
pass
|
||||
```
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from multilspy import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language
|
||||
from multilspy.multilspy_utils import SymbolUtils
|
||||
|
||||
|
||||
class TestGoLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_find_symbol(self, language_server: SyncLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoStruct"), "DemoStruct not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None:
|
||||
file_path = os.path.join("main.go")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
helper_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "Helper":
|
||||
helper_symbol = sym
|
||||
break
|
||||
assert helper_symbol is not None, "Could not find 'Helper' function symbol in main.go"
|
||||
sel_start = helper_symbol["selectionRange"]["start"]
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert any(
|
||||
"main.go" in ref.get("relativePath", "") for ref in refs
|
||||
), "main.go should reference Helper (tried all positions in selectionRange)"
|
||||
@@ -2,29 +2,21 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
from multilspy import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language
|
||||
|
||||
|
||||
def find_symbol_recursive(symbols, name):
|
||||
for symbol in symbols:
|
||||
if symbol.get("name") == name:
|
||||
return True
|
||||
if symbol.get("children"):
|
||||
if find_symbol_recursive(symbol["children"], name):
|
||||
return True
|
||||
return False
|
||||
from multilspy.multilspy_utils import SymbolUtils
|
||||
|
||||
|
||||
class TestJavaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
|
||||
def test_find_symbol(self, language_server):
|
||||
def test_find_symbol(self, language_server: SyncLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert find_symbol_recursive(symbols, "Main"), "Main class not found in symbol tree"
|
||||
assert find_symbol_recursive(symbols, "Utils"), "Utils class not found in symbol tree"
|
||||
assert find_symbol_recursive(symbols, "Model"), "Model class not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils class not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model class not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server):
|
||||
def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None:
|
||||
# Use correct Maven/Java file paths
|
||||
file_path = os.path.join("src", "main", "java", "test_repo", "Utils.java")
|
||||
refs = language_server.request_references(file_path, 4, 20)
|
||||
@@ -42,26 +34,16 @@ class TestJavaLanguageServer:
|
||||
# Use selectionRange if present, otherwise fall back to range
|
||||
if "selectionRange" in model_symbol:
|
||||
sel_start = model_symbol["selectionRange"]["start"]
|
||||
sel_end = model_symbol["selectionRange"]["end"]
|
||||
else:
|
||||
sel_start = model_symbol["range"]["start"]
|
||||
sel_end = model_symbol["range"]["end"]
|
||||
found = False
|
||||
if sel_start["line"] == sel_end["line"]:
|
||||
for col in range(sel_start["character"], sel_end["character"] + 1):
|
||||
refs = language_server.request_references(file_path, sel_start["line"], col)
|
||||
if any("Main.java" in ref.get("relativePath", "") for ref in refs):
|
||||
found = True
|
||||
else:
|
||||
refs_start = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
refs_end = language_server.request_references(file_path, sel_end["line"], sel_end["character"])
|
||||
if any("Main.java" in ref.get("relativePath", "") for ref in refs_start + refs_end):
|
||||
found = True
|
||||
assert found, "Main should reference Model (tried all positions in selectionRange)"
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert any(
|
||||
"Main.java" in ref.get("relativePath", "") for ref in refs
|
||||
), "Main should reference Model (tried all positions in selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
|
||||
def test_overview_methods(self, language_server):
|
||||
def test_overview_methods(self, language_server: SyncLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert find_symbol_recursive(symbols, "Main"), "Main missing from overview"
|
||||
assert find_symbol_recursive(symbols, "Utils"), "Utils missing from overview"
|
||||
assert find_symbol_recursive(symbols, "Model"), "Model missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
|
||||
@@ -18,7 +18,7 @@ class TestLanguageServerBasics:
|
||||
"""Test basic functionality of the language server."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_references_user_class(self, language_server: SyncLanguageServer):
|
||||
def test_request_references_user_class(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_references on the User class."""
|
||||
# Get references to the User class in models.py
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
@@ -33,7 +33,7 @@ class TestLanguageServerBasics:
|
||||
assert len(references) > 1, "User class should be referenced in multiple files (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_references_item_class(self, language_server: SyncLanguageServer):
|
||||
def test_request_references_item_class(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_references on the Item class."""
|
||||
# Get references to the Item class in models.py
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
@@ -49,7 +49,7 @@ class TestLanguageServerBasics:
|
||||
assert len(services_references) > 0, "At least one reference should be in services.py (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_references_function_parameter(self, language_server: SyncLanguageServer):
|
||||
def test_request_references_function_parameter(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_references on a function parameter."""
|
||||
# Get references to the id parameter in get_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -64,7 +64,7 @@ class TestLanguageServerBasics:
|
||||
assert len(references) > 0, "id parameter should be referenced within the method (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_references_create_user_method(self, language_server: SyncLanguageServer):
|
||||
def test_request_references_create_user_method(self, language_server: SyncLanguageServer) -> None:
|
||||
# Get references to the create_user method in UserService
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 15 contains the create_user method definition
|
||||
@@ -78,7 +78,7 @@ class TestLanguageServerBasics:
|
||||
assert len(references) > 1, "Should get valid references for create_user (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_retrieve_content_around_line(self, language_server: SyncLanguageServer):
|
||||
def test_retrieve_content_around_line(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test retrieve_content_around_line functionality with various scenarios."""
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
|
||||
@@ -185,7 +185,7 @@ class TestLanguageServerBasics:
|
||||
assert line.match_type == LineType.AFTER_MATCH
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_search_files_for_pattern(self, language_server: SyncLanguageServer):
|
||||
def test_search_files_for_pattern(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test search_files_for_pattern with various patterns and glob filters."""
|
||||
# Test 1: Search for class definitions across all files
|
||||
class_pattern = r"class\s+\w+\s*(?:\([^{]*\)|:)"
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestLanguageServerSymbols:
|
||||
"""Test the language server's symbol-related functionality."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_containing_symbol_function(self, language_server: SyncLanguageServer):
|
||||
def test_request_containing_symbol_function(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
# Test for a position inside the create_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -34,7 +34,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["body"].strip().startswith("def create_user(self")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_references_to_variables(self, language_server: SyncLanguageServer):
|
||||
def test_references_to_variables(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a variable."""
|
||||
file_path = os.path.join("test_repo", "variables.py")
|
||||
# Line 75 contains the field status that is later modified
|
||||
@@ -49,7 +49,7 @@ class TestLanguageServerSymbols:
|
||||
assert "second_dataclass" in ref_names
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_containing_symbol_class(self, language_server: SyncLanguageServer):
|
||||
def test_request_containing_symbol_class(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a class."""
|
||||
# Test for a position inside the UserService class but outside any method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -62,7 +62,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_containing_symbol_nested(self, language_server: SyncLanguageServer):
|
||||
def test_request_containing_symbol_nested(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
# Test for a position inside a method which is inside a class
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -88,7 +88,7 @@ class TestLanguageServerSymbols:
|
||||
assert parent_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_containing_symbol_none(self, language_server: SyncLanguageServer):
|
||||
def test_request_containing_symbol_none(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a position with no containing symbol."""
|
||||
# Test for a position outside any function/class (e.g., in imports)
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -99,7 +99,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol is None or containing_symbol == {}
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_referencing_symbols_function(self, language_server: SyncLanguageServer):
|
||||
def test_request_referencing_symbols_function(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function."""
|
||||
# Test referencing symbols for create_user function
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -121,7 +121,7 @@ class TestLanguageServerSymbols:
|
||||
assert "end" in symbol["location"]["range"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_referencing_symbols_class(self, language_server: SyncLanguageServer):
|
||||
def test_request_referencing_symbols_class(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a class."""
|
||||
# Test referencing symbols for User class
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
@@ -140,7 +140,7 @@ class TestLanguageServerSymbols:
|
||||
assert len(services_references) > 0, "No referencing symbols from services.py for User (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_referencing_symbols_parameter(self, language_server: SyncLanguageServer):
|
||||
def test_request_referencing_symbols_parameter(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function parameter."""
|
||||
# Test referencing symbols for id parameter in get_user
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -154,14 +154,12 @@ class TestLanguageServerSymbols:
|
||||
method_refs = [
|
||||
symbol
|
||||
for symbol in ref_symbols
|
||||
if "location" in symbol
|
||||
and "range" in symbol["location"]
|
||||
and symbol["location"]["range"]["start"]["line"] > sel_start["line"]
|
||||
if "location" in symbol and "range" in symbol["location"] and symbol["location"]["range"]["start"]["line"] > sel_start["line"]
|
||||
]
|
||||
assert len(method_refs) > 0, "No referencing symbols within method body for get_user (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_referencing_symbols_none(self, language_server: SyncLanguageServer):
|
||||
def test_request_referencing_symbols_none(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a position with no symbol."""
|
||||
# For positions with no symbol, the method might throw an error or return None/empty list
|
||||
# We'll modify our test to handle this by using a try-except block
|
||||
@@ -179,7 +177,7 @@ class TestLanguageServerSymbols:
|
||||
|
||||
# Tests for request_defining_symbol
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_defining_symbol_variable(self, language_server: SyncLanguageServer):
|
||||
def test_request_defining_symbol_variable(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a variable usage."""
|
||||
# Test finding the definition of a symbol in the create_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -198,7 +196,7 @@ class TestLanguageServerSymbols:
|
||||
assert "services.py" in defining_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_defining_symbol_imported_class(self, language_server: SyncLanguageServer):
|
||||
def test_request_defining_symbol_imported_class(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for an imported class."""
|
||||
# Test finding the definition of the 'User' class used in the UserService.create_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -210,7 +208,7 @@ class TestLanguageServerSymbols:
|
||||
assert defining_symbol.get("name") == "User"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_defining_symbol_method_call(self, language_server: SyncLanguageServer):
|
||||
def test_request_defining_symbol_method_call(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a method call."""
|
||||
# Create an example file path for a file that calls UserService.create_user
|
||||
examples_file_path = os.path.join("examples", "user_management.py")
|
||||
@@ -235,7 +233,7 @@ class TestLanguageServerSymbols:
|
||||
warnings.warn("Could not verify method call definition - file structure may differ from expected")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_defining_symbol_none(self, language_server: SyncLanguageServer):
|
||||
def test_request_defining_symbol_none(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a position with no symbol."""
|
||||
# Test for a position with no symbol (e.g., whitespace or comment)
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -246,7 +244,7 @@ class TestLanguageServerSymbols:
|
||||
assert defining_symbol is None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_containing_symbol_variable(self, language_server: SyncLanguageServer):
|
||||
def test_request_containing_symbol_variable(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_containing_symbol where the symbol is a variable."""
|
||||
# Test for a position inside a variable definition
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -259,7 +257,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["kind"] == SymbolKind.Variable
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_defining_symbol_nested_function(self, language_server: SyncLanguageServer):
|
||||
def test_request_defining_symbol_nested_function(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a nested function or closure."""
|
||||
# Use the existing nested.py file which contains nested classes and methods
|
||||
file_path = os.path.join("test_repo", "nested.py")
|
||||
@@ -310,7 +308,7 @@ class TestLanguageServerSymbols:
|
||||
assert defining_symbol.get("kind") == SymbolKind.Function.value
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_symbol_methods_integration(self, language_server: SyncLanguageServer):
|
||||
def test_symbol_methods_integration(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test the integration between different symbol-related methods."""
|
||||
# This test demonstrates using the various symbol methods together
|
||||
# by finding a symbol and then checking its definition
|
||||
@@ -359,7 +357,7 @@ class TestLanguageServerSymbols:
|
||||
warnings.warn("Could not verify container hierarchy - implementation detail")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_symbol_tree_structure(self, language_server: SyncLanguageServer):
|
||||
def test_symbol_tree_structure(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
repo_structure = language_server.request_full_symbol_tree()
|
||||
@@ -387,7 +385,7 @@ class TestLanguageServerSymbols:
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_symbol_tree_structure_subdir(self, language_server: SyncLanguageServer):
|
||||
def test_symbol_tree_structure_subdir(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
examples_package_roots = language_server.request_full_symbol_tree(within_relative_path="examples")
|
||||
@@ -408,7 +406,7 @@ class TestLanguageServerSymbols:
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_dir_overview(self, language_server: SyncLanguageServer):
|
||||
def test_request_dir_overview(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test that request_dir_overview returns correct symbol information for files in a directory."""
|
||||
# Get overview of the examples directory
|
||||
overview = language_server.request_dir_overview("test_repo")
|
||||
@@ -433,7 +431,7 @@ class TestLanguageServerSymbols:
|
||||
assert symbol in services_symbols
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_request_document_overview(self, language_server: SyncLanguageServer):
|
||||
def test_request_document_overview(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test that request_document_overview returns correct symbol information for a file."""
|
||||
# Get overview of the user_management.py file
|
||||
overview = language_server.request_document_overview(os.path.join("examples", "user_management.py"))
|
||||
@@ -443,7 +441,7 @@ class TestLanguageServerSymbols:
|
||||
assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
def test_containing_symbol_of_var_is_file(self, language_server: SyncLanguageServer):
|
||||
def test_containing_symbol_of_var_is_file(self, language_server: SyncLanguageServer) -> None:
|
||||
"""Test that the containing symbol of a variable is the file itself."""
|
||||
# Get the containing symbol of a variable in a file
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
|
||||
@@ -2,22 +2,14 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
from multilspy import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language
|
||||
|
||||
|
||||
def find_symbol_recursive(symbols, name):
|
||||
for symbol in symbols:
|
||||
if symbol.get("name") == name:
|
||||
return True
|
||||
if symbol.get("children"):
|
||||
if find_symbol_recursive(symbol["children"], name):
|
||||
return True
|
||||
return False
|
||||
from multilspy.multilspy_utils import SymbolUtils
|
||||
|
||||
|
||||
class TestRustLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
|
||||
def test_find_references_raw(self, language_server):
|
||||
def test_find_references_raw(self, language_server: SyncLanguageServer) -> None:
|
||||
# Directly test the request_references method for the add function
|
||||
file_path = os.path.join("src", "lib.rs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
@@ -28,32 +20,20 @@ class TestRustLanguageServer:
|
||||
break
|
||||
assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
|
||||
sel_start = add_symbol["selectionRange"]["start"]
|
||||
sel_end = add_symbol["selectionRange"]["end"]
|
||||
found = False
|
||||
if sel_start["line"] == sel_end["line"]:
|
||||
for col in range(sel_start["character"], sel_end["character"] + 1):
|
||||
refs = language_server.request_references(file_path, sel_start["line"], col)
|
||||
# f"Raw request_references for add in lib.rs at col {col}: {refs}")
|
||||
if any("main.rs" in ref.get("relativePath", "") for ref in refs):
|
||||
found = True
|
||||
else:
|
||||
refs_start = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
refs_end = language_server.request_references(file_path, sel_end["line"], sel_end["character"])
|
||||
# f"Raw request_references for add in lib.rs at selectionRange start: {refs_start}")
|
||||
# f"Raw request_references for add in lib.rs at selectionRange end: {refs_end}")
|
||||
if any("main.rs" in ref.get("relativePath", "") for ref in refs_start + refs_end):
|
||||
found = True
|
||||
assert found, "main.rs should reference add (raw, tried all positions in selectionRange)"
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert any(
|
||||
"main.rs" in ref.get("relativePath", "") for ref in refs
|
||||
), "main.rs should reference add (raw, tried all positions in selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
|
||||
def test_find_symbol(self, language_server):
|
||||
def test_find_symbol(self, language_server: SyncLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert find_symbol_recursive(symbols, "main"), "main function not found in symbol tree"
|
||||
assert find_symbol_recursive(symbols, "add"), "add function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree"
|
||||
# Add more as needed based on test_repo
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server):
|
||||
def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None:
|
||||
# Find references to 'add' defined in lib.rs, should be referenced from main.rs
|
||||
file_path = os.path.join("src", "lib.rs")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
@@ -64,28 +44,13 @@ class TestRustLanguageServer:
|
||||
break
|
||||
assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
|
||||
sel_start = add_symbol["selectionRange"]["start"]
|
||||
sel_end = add_symbol["selectionRange"]["end"]
|
||||
# f"add symbol selectionRange: start={sel_start}, end={sel_end}")
|
||||
found = False
|
||||
# Try all character positions in the selectionRange (same line, from start to end character)
|
||||
if sel_start["line"] == sel_end["line"]:
|
||||
for col in range(sel_start["character"], sel_end["character"] + 1):
|
||||
refs = language_server.request_references(file_path, sel_start["line"], col)
|
||||
# f"References for add in lib.rs at col {col} (selectionRange): {refs}")
|
||||
if any("main.rs" in ref.get("relativePath", "") for ref in refs):
|
||||
found = True
|
||||
else:
|
||||
# If the function name spans multiple lines, try start and end
|
||||
refs_start = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
refs_end = language_server.request_references(file_path, sel_end["line"], sel_end["character"])
|
||||
# f"References for add in lib.rs at selectionRange start: {refs_start}")
|
||||
# f"References for add in lib.rs at selectionRange end: {refs_end}")
|
||||
if any("main.rs" in ref.get("relativePath", "") for ref in refs_start + refs_end):
|
||||
found = True
|
||||
assert found, "main.rs should reference add (tried all positions in selectionRange)"
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert any(
|
||||
"main.rs" in ref.get("relativePath", "") for ref in refs
|
||||
), "main.rs should reference add (tried all positions in selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
|
||||
def test_overview_methods(self, language_server):
|
||||
def test_overview_methods(self, language_server: SyncLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert find_symbol_recursive(symbols, "main"), "main missing from overview"
|
||||
assert find_symbol_recursive(symbols, "add"), "add missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"
|
||||
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from multilspy import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language
|
||||
from multilspy.multilspy_utils import SymbolUtils
|
||||
|
||||
|
||||
class TestTypescriptLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_find_symbol(self, language_server: SyncLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoClass"), "DemoClass not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "helperFunction"), "helperFunction not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "printValue"), "printValue method not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SyncLanguageServer) -> None:
|
||||
file_path = os.path.join("index.ts")
|
||||
symbols = language_server.request_document_symbols(file_path)
|
||||
helper_symbol = None
|
||||
for sym in symbols[0]:
|
||||
if sym.get("name") == "helperFunction":
|
||||
helper_symbol = sym
|
||||
break
|
||||
assert helper_symbol is not None, "Could not find 'helperFunction' symbol in index.ts"
|
||||
sel_start = helper_symbol["selectionRange"]["start"]
|
||||
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert any(
|
||||
"index.ts" in ref.get("relativePath", "") for ref in refs
|
||||
), "index.ts should reference helperFunction (tried all positions in selectionRange)"
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, Go!")
|
||||
Helper()
|
||||
}
|
||||
|
||||
func Helper() {
|
||||
fmt.Println("Helper function called")
|
||||
}
|
||||
|
||||
type DemoStruct struct {
|
||||
Field int
|
||||
}
|
||||
|
||||
func UsingHelper() {
|
||||
Helper()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export class DemoClass {
|
||||
value: number;
|
||||
constructor(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
printValue() {
|
||||
console.log(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export function helperFunction() {
|
||||
const demo = new DemoClass(42);
|
||||
demo.printValue();
|
||||
}
|
||||
|
||||
helperFunction();
|
||||
Reference in new issue
Block a user