mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 22:16:23 +00:00
Fix symbol name resolution in selected LS (#1247)
Adds tests for name resolution for each language and fixes the name resolution where tests have failed. Also: Extends python tests to cover ty Extends skipping conditions for several LS (but only when not in CI) Better test output in CI Removed a wrong python test (started failing for ty and should have failed for pyright too)
This commit is contained in:
1 parent
7377a84337
commit
d30cdf8474
59 files changed
+1015
-97
No files matched your search
@@ -566,7 +566,7 @@ jobs:
|
||||
df -h
|
||||
- name: Test with pytest
|
||||
shell: bash
|
||||
run: uv run poe test
|
||||
run: uv run poe test -q --tb=short
|
||||
- name: Type-checking with mypy
|
||||
shell: bash
|
||||
run: uv run poe type-check
|
||||
@@ -1013,9 +1013,18 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
original_name = symbol["name"]
|
||||
normalized_name = self._extract_al_display_name(original_name)
|
||||
|
||||
# Store original name if it was normalized (for hover injection)
|
||||
if symbol.get("kind") in (SymbolKind.Function, SymbolKind.Method) and "(" in normalized_name:
|
||||
normalized_name = normalized_name.split("(", 1)[0].strip()
|
||||
|
||||
if symbol.get("kind") == SymbolKind.Method and normalized_name.lower().startswith("action "):
|
||||
normalized_name = normalized_name.split(None, 1)[-1].strip()
|
||||
|
||||
if symbol.get("kind") == SymbolKind.Field and ":" in normalized_name:
|
||||
normalized_name = normalized_name.split(":", 1)[0].strip()
|
||||
|
||||
# Store original name if it was normalized for an AL object declaration
|
||||
# Only store if we have valid position data to avoid false matches at (0, 0)
|
||||
if original_name != normalized_name:
|
||||
if original_name != normalized_name and self._AL_OBJECT_NAME_PATTERN.match(original_name):
|
||||
sel_range = symbol.get("selectionRange")
|
||||
if sel_range:
|
||||
start = sel_range.get("start") # type: ignore
|
||||
@@ -1026,6 +1035,11 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
|
||||
return normalized_name
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> int:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def request_hover(
|
||||
self, relative_file_path: str, line: int, column: int, file_buffer: LSPFileBuffer | None = None
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
from collections.abc import Hashable
|
||||
from typing import cast
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
@@ -34,6 +37,15 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
config, repository_root_path, ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), "dart", solidlsp_settings
|
||||
)
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
return symbol["name"].rsplit(".", 1)[-1]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
dart_settings = solidlsp_settings.get_ls_specific_settings(Language.DART)
|
||||
|
||||
@@ -4,11 +4,12 @@ import pathlib
|
||||
import stat
|
||||
import subprocess
|
||||
import threading
|
||||
from collections.abc import Hashable
|
||||
from typing import Any, cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
@@ -215,6 +216,15 @@ class ElixirTools(SolidLanguageServer):
|
||||
# Set generous timeout for Expert which can be slow to initialize and respond
|
||||
self.set_request_timeout(180.0)
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
return symbol["name"].removeprefix("defp ").removeprefix("def ").split("(", 1)[0].strip()
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
from collections.abc import Hashable
|
||||
from typing import Any, cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -165,15 +168,13 @@ class Gopls(SolidLanguageServer):
|
||||
_CACHE_CONTEXT_ENV_KEYS = ("GOFLAGS", "GOOS", "GOARCH", "CGO_ENABLED")
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> str | None:
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
"""
|
||||
Compute a deterministic fingerprint of the Go build context.
|
||||
|
||||
The fingerprint includes gopls_settings and selected env vars that affect symbol discovery.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
normalize_symbol_name_version = 1
|
||||
gopls_settings_raw = self._custom_settings.settings.get("gopls_settings")
|
||||
|
||||
gopls_settings: dict | None
|
||||
@@ -190,11 +191,14 @@ class Gopls(SolidLanguageServer):
|
||||
if value:
|
||||
env_subset[key] = value
|
||||
|
||||
# Return None only when BOTH settings and env subset are effectively empty.
|
||||
# Version processed symbols even when the build context itself is empty.
|
||||
if gopls_settings is None and not env_subset:
|
||||
return None
|
||||
return normalize_symbol_name_version
|
||||
|
||||
fingerprint_data: dict[str, object] = {"env": env_subset}
|
||||
fingerprint_data: dict[str, object] = {
|
||||
"env": env_subset,
|
||||
"normalize_symbol_name_version": normalize_symbol_name_version,
|
||||
}
|
||||
if gopls_settings is not None:
|
||||
fingerprint_data["gopls_settings"] = gopls_settings
|
||||
|
||||
@@ -202,6 +206,10 @@ class Gopls(SolidLanguageServer):
|
||||
|
||||
return hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
return symbol["name"].rsplit(".", 1)[-1]
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""Start gopls server process"""
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ import os
|
||||
import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
from collections.abc import Hashable
|
||||
from pathlib import Path
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -169,6 +171,26 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
)
|
||||
self.request_id = 0
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
|
||||
if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method):
|
||||
return original_name
|
||||
|
||||
if "." in original_name:
|
||||
return original_name.rsplit(".", 1)[-1]
|
||||
|
||||
if ":" in original_name:
|
||||
return original_name.rsplit(":", 1)[-1]
|
||||
|
||||
return original_name
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -14,12 +14,14 @@ import platform
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
from collections.abc import Hashable
|
||||
from pathlib import Path
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -199,6 +201,30 @@ class PowerShellLanguageServer(SolidLanguageServer):
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
symbol_kind = symbol.get("kind")
|
||||
|
||||
# normalize class declarations
|
||||
if symbol_kind == SymbolKind.Class:
|
||||
return original_name.removeprefix("class ").split("{", 1)[0].strip()
|
||||
|
||||
# normalize method signatures
|
||||
if symbol_kind == SymbolKind.Method:
|
||||
return original_name.split("(", 1)[0].rsplit(None, 1)[-1]
|
||||
|
||||
# normalize function declarations
|
||||
if symbol_kind == SymbolKind.Function:
|
||||
return original_name.removeprefix("function ").split("(", 1)[0].strip()
|
||||
|
||||
return original_name
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -3,12 +3,14 @@ import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
from collections.abc import Hashable
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -56,6 +58,23 @@ class SourceKitLSP(SolidLanguageServer):
|
||||
self._did_sleep_before_requesting_references = False
|
||||
self._initialization_timestamp: float | None = None
|
||||
|
||||
@override
|
||||
def _document_symbols_cache_fingerprint(self) -> Hashable:
|
||||
normalize_symbol_name_version = 1
|
||||
return normalize_symbol_name_version
|
||||
|
||||
@override
|
||||
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
|
||||
original_name = symbol["name"]
|
||||
|
||||
if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method, SymbolKind.Constructor):
|
||||
return original_name
|
||||
|
||||
if "(" not in original_name:
|
||||
return original_name
|
||||
|
||||
return original_name.split("(", 1)[0].strip()
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
@@ -41,6 +41,7 @@ _LANGUAGE_REPO_ALIASES: dict[Language, Language] = {
|
||||
Language.PHP_PHPACTOR: Language.PHP,
|
||||
Language.PYTHON_JEDI: Language.PYTHON,
|
||||
Language.RUBY_SOLARGRAPH: Language.RUBY,
|
||||
Language.PYTHON_TY: Language.PYTHON,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ type DemoStruct struct {
|
||||
Field int
|
||||
}
|
||||
|
||||
func (d *DemoStruct) Value() int {
|
||||
return d.Field
|
||||
}
|
||||
|
||||
func UsingHelper() {
|
||||
Helper()
|
||||
}
|
||||
@@ -105,5 +105,11 @@ function Main {
|
||||
Write-Output "Script completed successfully"
|
||||
}
|
||||
|
||||
class PersonFormatter {
|
||||
[string] FormatName([string]$Name) {
|
||||
return "Formatted: $Name"
|
||||
}
|
||||
}
|
||||
|
||||
# Run main function
|
||||
Main @args
|
||||
@@ -187,7 +187,7 @@ class TestSerenaAgent:
|
||||
pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
|
||||
pytest.param(Language.CLOJURE, "greet", "Function", clj.CORE_PATH, marks=pytest.mark.clojure),
|
||||
pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
|
||||
pytest.param(Language.POWERSHELL, "function Greet-User ()", "Function", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.POWERSHELL, "Greet-User", "Function", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.CPP_CCLS, "add", "Function", "b.cpp", marks=pytest.mark.cpp),
|
||||
pytest.param(Language.LEAN4, "add", "Method", "Helper.lean", marks=pytest.mark.lean4),
|
||||
],
|
||||
@@ -288,7 +288,7 @@ class TestSerenaAgent:
|
||||
marks=pytest.mark.clojure,
|
||||
),
|
||||
pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
|
||||
pytest.param(Language.POWERSHELL, "function Greet-User ()", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.POWERSHELL, "Greet-User", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
|
||||
pytest.param(Language.CPP_CCLS, "add", "b.cpp", "a.cpp", marks=pytest.mark.cpp),
|
||||
pytest.param(Language.LEAN4, "add", "Helper.lean", "Main.lean", marks=pytest.mark.lean4),
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
from serena.jetbrains.jetbrains_types import SymbolDTO, SymbolDTOKey
|
||||
from serena.project import Project
|
||||
from serena.symbol import LanguageServerSymbol, LanguageServerSymbolRetriever, NamePathComponent, NamePathMatcher
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
|
||||
class TestSymbolNameMatching:
|
||||
@@ -235,7 +235,7 @@ class TestSymbolNameMatching:
|
||||
|
||||
@pytest.mark.python
|
||||
class TestLanguageServerSymbolRetriever:
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_info(self, project_with_ls: Project):
|
||||
symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
|
||||
create_user_method_symbol = symbol_retriever.find("UserService/create_user", within_relative_path="test_repo/services.py")[0]
|
||||
@@ -280,7 +280,7 @@ def _make_mock_symbols(count: int, *, relative_path: str = "test_repo/services.p
|
||||
class TestHoverBudget:
|
||||
"""Tests for symbol_info_budget time budget behavior."""
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_budget_not_exceeded_all_lookups_performed(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""With a large budget, all hover lookups are performed."""
|
||||
# Create symbol retriever with a mock agent that has large budget
|
||||
@@ -309,7 +309,7 @@ class TestHoverBudget:
|
||||
assert all(info is not None for info in result.values())
|
||||
assert len(result) == 3
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_budget_exceeded_partial_info(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""With a small budget, hover lookups stop and remaining symbols get None info."""
|
||||
project_with_ls.serena_config.symbol_info_budget = 0.1
|
||||
@@ -353,7 +353,7 @@ class TestHoverBudget:
|
||||
assert result_list[3] is None
|
||||
assert result_list[4] is None
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_budget_zero_means_unlimited(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""With budget=0, all hover lookups proceed (no early stopping)."""
|
||||
project_with_ls.serena_config.symbol_info_budget = 0.0
|
||||
@@ -380,7 +380,7 @@ class TestHoverBudget:
|
||||
assert call_count == 5
|
||||
assert all(info is not None for info in result.values())
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_project_budget_overrides_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Project-level budget overrides global budget."""
|
||||
# Create symbol retriever with global budget 10.0 but project budget 0.05
|
||||
@@ -418,7 +418,7 @@ class TestHoverBudget:
|
||||
# So 2 calls succeed (proving project budget 0.05 overrode global 10.0)
|
||||
assert call_count == 2
|
||||
|
||||
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_project_null_inherits_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
|
||||
"""When project budget is None, global budget is used."""
|
||||
# Create symbol retriever with project budget=None (inherit global)
|
||||
|
||||
@@ -6,8 +6,10 @@ from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.language_servers.al_language_server import ALLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [pytest.mark.al, pytest.mark.skipif(not language_tests_enabled(Language.AL), reason="AL tests are disabled")]
|
||||
|
||||
@@ -556,3 +558,38 @@ class TestALPathNormalization:
|
||||
f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
|
||||
)
|
||||
break
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
is_al_display_symbol = (
|
||||
s["name"].endswith((".Codeunit", ".Page", ".Table", ".TableExt", ".Enum", ".Interface"))
|
||||
or (s["name"].startswith('"') and s["name"].endswith('"'))
|
||||
or s["name"].startswith(
|
||||
(
|
||||
"Enum Name ",
|
||||
"Area ",
|
||||
"Group ",
|
||||
"Field ",
|
||||
"Part ",
|
||||
"SystemPart ",
|
||||
"Repeater ",
|
||||
"ActionRef ",
|
||||
"Key ",
|
||||
"FieldGroup ",
|
||||
)
|
||||
)
|
||||
or ":" in s["name"]
|
||||
)
|
||||
if not is_al_display_symbol and has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["kind"] in {SymbolKind.Class, SymbolKind.Struct, SymbolKind.Interface, SymbolKind.Enum},
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -13,6 +13,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(platform.system() == "Windows", reason="ansible-language-server does not support native Windows operation")
|
||||
@@ -51,3 +52,17 @@ class TestAnsibleLanguageServerBasics:
|
||||
assert len(result) > 0, "Expected non-empty completion list"
|
||||
labels = [item["completionText"] for item in result if "completionText" in item]
|
||||
assert labels, f"Expected completions with completionText, got: {result[:3]}"
|
||||
|
||||
@pytest.mark.xfail(reason="Seems like ansible LS lacks basic functionality at the moment, textDocument/documentSymbol doesn't work")
|
||||
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.bash
|
||||
@@ -119,3 +120,16 @@ class TestBashLanguageServerBasics:
|
||||
# Verify total counts match expectations
|
||||
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.sh, found {len(main_functions)}"
|
||||
assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_functions)}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -3,8 +3,9 @@ import pytest
|
||||
from serena.project import Project
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import UnifiedSymbolInformation
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
from . import CORE_PATH, UTILS_PATH
|
||||
|
||||
@@ -187,6 +188,8 @@ class TestLanguageServerBasics:
|
||||
assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled")
|
||||
@pytest.mark.clojure
|
||||
class TestProjectBasics:
|
||||
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
|
||||
def test_retrieve_content_around_line(self, project: Project):
|
||||
@@ -221,3 +224,20 @@ class TestProjectBasics:
|
||||
assert result is not None, "Should find require statements"
|
||||
utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path]
|
||||
assert len(utils_matches) > 0, "Should find require statement in utils.clj"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
# clojure-lsp exposes namespace and dependency/container entries in addition to real vars/functions.
|
||||
# Those qualified names are not the target of this regression check.
|
||||
if s["kind"] in {SymbolKind.Namespace, SymbolKind.Struct}:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
|
||||
PYTHON_BACKEND_LANGUAGES = [Language.PYTHON, Language.PYTHON_TY]
|
||||
|
||||
|
||||
def has_malformed_name(
|
||||
symbol: UnifiedSymbolInformation,
|
||||
whitespace_allowed: bool = False,
|
||||
period_allowed: bool = False,
|
||||
colon_allowed: bool = False,
|
||||
brace_allowed: bool = False,
|
||||
parenthesis_allowed: bool = False,
|
||||
comma_allowed: bool = False,
|
||||
) -> bool:
|
||||
forbidden_chars: list[str] = []
|
||||
|
||||
if not whitespace_allowed:
|
||||
forbidden_chars.append(" ")
|
||||
if not period_allowed:
|
||||
forbidden_chars.append(".")
|
||||
if not colon_allowed:
|
||||
forbidden_chars.append(":")
|
||||
if not brace_allowed:
|
||||
forbidden_chars.append("{")
|
||||
if not parenthesis_allowed:
|
||||
forbidden_chars.append("(")
|
||||
if not comma_allowed:
|
||||
forbidden_chars.append(",")
|
||||
|
||||
return any(separator in symbol["name"] for separator in forbidden_chars)
|
||||
|
||||
|
||||
def request_all_symbols(language_server: SolidLanguageServer) -> list[UnifiedSymbolInformation]:
|
||||
result: list[UnifiedSymbolInformation] = []
|
||||
|
||||
def visit(symbol: UnifiedSymbolInformation) -> None:
|
||||
result.append(symbol)
|
||||
for child in symbol.get("children", []):
|
||||
visit(child)
|
||||
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
for symbol in symbols:
|
||||
visit(symbol)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def format_symbol_for_assert(symbol: UnifiedSymbolInformation) -> str:
|
||||
relative_path = symbol.get("location", {}).get("relativePath", "<unknown>")
|
||||
try:
|
||||
kind = SymbolKind(symbol["kind"]).name
|
||||
except ValueError:
|
||||
kind = str(symbol["kind"])
|
||||
return f"{symbol['name']} [{kind}] ({relative_path})"
|
||||
@@ -15,6 +15,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _ccls_available() -> bool:
|
||||
@@ -142,3 +143,16 @@ int use_add() {
|
||||
# Clean up the new file
|
||||
if os.path.exists(new_file_abs_path):
|
||||
os.remove(new_file_abs_path)
|
||||
|
||||
@pytest.mark.parametrize("language_server", _cpp_servers, indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -15,8 +15,10 @@ from solidlsp.language_servers.csharp_language_server import (
|
||||
find_solution_or_project_file,
|
||||
)
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.csharp
|
||||
@@ -359,3 +361,16 @@ class TestCSharpSolutionProjectOpening:
|
||||
|
||||
# Verify the file actually exists
|
||||
assert os.path.exists(result)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.Namespace):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.dart
|
||||
@@ -361,3 +362,16 @@ class TestDartLanguageServer:
|
||||
if main_dart_refs:
|
||||
for ref in main_dart_refs:
|
||||
assert "range" in ref or "location" in ref
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,8 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
|
||||
|
||||
@@ -110,3 +112,29 @@ class TestElixirBasic:
|
||||
for _ in range(3):
|
||||
symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
|
||||
assert symbols is not None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] in {SymbolKind.Module, SymbolKind.Namespace, SymbolKind.Struct}:
|
||||
continue
|
||||
|
||||
allow_test_style_name = s["name"].startswith(('test "', 'describe "'))
|
||||
allow_struct_name = s["name"].startswith("%")
|
||||
allow_type_name = s["name"].startswith("@type ")
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=allow_test_style_name or allow_type_name,
|
||||
period_allowed=allow_struct_name,
|
||||
colon_allowed=allow_test_style_name,
|
||||
brace_allowed=allow_test_style_name or allow_struct_name,
|
||||
comma_allowed=allow_test_style_name,
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,12 +1,16 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("elm") is None and not is_ci, reason="Elm compiler is not available")
|
||||
@pytest.mark.elm
|
||||
class TestElmLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
|
||||
@@ -52,3 +56,16 @@ class TestElmLanguageServer:
|
||||
|
||||
# Verify that at least one reference is in Main.elm (where formatMessage is used)
|
||||
assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Expected to find usage of formatMessage in Main.elm"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON
|
||||
|
||||
@@ -41,3 +42,16 @@ class TestErlangLanguageServerBasics:
|
||||
pytest.skip("Erlang language server not fully initialized")
|
||||
else:
|
||||
raise
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Mark all tests in this module as fortran tests
|
||||
pytestmark = pytest.mark.fortran
|
||||
@@ -272,3 +273,16 @@ class TestFortranLanguageServer:
|
||||
_refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
# refs might be empty if Point3D isn't used elsewhere, but the call should not fail
|
||||
# The important thing is that it doesn't error due to wrong character position
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
# Currently, most F# tests fail, there seems to be a regression or instability.
|
||||
@@ -183,3 +184,16 @@ class TestFSharpLanguageServer:
|
||||
|
||||
# This is a successful test - FsAutoComplete is working with F# files
|
||||
assert True, "F# language server can handle files successfully"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -3,9 +3,11 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.go
|
||||
@@ -17,6 +19,20 @@ class TestGoLanguageServer:
|
||||
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_symbol_matches_go_method_by_bare_name(self, language_server: SolidLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree(within_relative_path="main.go")
|
||||
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Value"), "Expected Go method name to be normalized to bare name"
|
||||
assert not SymbolUtils.symbol_tree_contains_name(symbols, "(*DemoStruct).Value"), (
|
||||
"Expected receiver-qualified Go method name to be normalized away"
|
||||
)
|
||||
|
||||
bare_name_matches = [match for root in symbols for match in LanguageServerSymbol(root).find("Value")]
|
||||
|
||||
assert bare_name_matches, "Expected a Go method to match by bare name"
|
||||
assert all(match.name == "Value" for match in bare_name_matches)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("main.go")
|
||||
@@ -192,3 +208,16 @@ class TestGoBuildTags:
|
||||
|
||||
# A cache miss should repopulate and mark caches modified.
|
||||
_assert_caches_modified(ls_foo)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.groovy
|
||||
@@ -103,3 +104,16 @@ class TestGroovyLanguageServer:
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser missing from overview"
|
||||
|
||||
def test_bare_symbol_names(self) -> None:
|
||||
assert self.language_server is not None
|
||||
all_symbols = request_all_symbols(self.language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -19,6 +19,8 @@ import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.haskell
|
||||
@@ -217,3 +219,24 @@ class TestHaskellLanguageServer:
|
||||
assert any("Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths), (
|
||||
f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Module:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
diagnostics = [
|
||||
{
|
||||
"formatted": format_symbol_for_assert(sym),
|
||||
"name": sym["name"],
|
||||
"kind": SymbolKind(sym["kind"]).name,
|
||||
"detail": sym.get("detail"),
|
||||
}
|
||||
for sym in malformed_symbols
|
||||
]
|
||||
pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)
|
||||
@@ -12,7 +12,9 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
|
||||
@@ -158,3 +160,16 @@ class TestHlslHover:
|
||||
hover_info = language_server.request_hover("common.hlsl", 3, 7)
|
||||
assert hover_info is not None, "Hover should return information for VertexInput"
|
||||
assert "contents" in hover_info, "Hover should have contents"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.File):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -6,6 +6,7 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [pytest.mark.java, pytest.mark.skipif(not language_tests_enabled(Language.JAVA), reason="Java tests disabled")]
|
||||
|
||||
@@ -50,3 +51,16 @@ class TestJavaLanguageServer:
|
||||
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"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,9 +1,14 @@
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("julia") is None and not is_ci, reason="Julia is not available")
|
||||
@pytest.mark.julia
|
||||
class TestJuliaLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
|
||||
@@ -48,3 +53,16 @@ class TestJuliaLanguageServer:
|
||||
reference_paths = [ref["relativePath"] for ref in references]
|
||||
# The reference might be in either file (definition or usage)
|
||||
assert "main.jl" in reference_paths or "lib/helper.jl" in reference_paths
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -4,8 +4,10 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
# Kotlin LSP (IntelliJ-based, pre-alpha v261) crashes on JVM restart under CI resource constraints
|
||||
@@ -55,3 +57,16 @@ class TestKotlinLanguageServer:
|
||||
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"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.File):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -10,6 +10,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.lua
|
||||
@@ -253,3 +254,21 @@ class TestLuaLanguageServer:
|
||||
# The test file should have some content that references calculator
|
||||
symbol_list = test_symbols[0] if isinstance(test_symbols, tuple) else test_symbols
|
||||
assert len(symbol_list) > 0, "test_calculator.lua should have symbols"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["name"] == " ",
|
||||
period_allowed="." in s["name"],
|
||||
colon_allowed=":" in s["name"],
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.luau
|
||||
@@ -151,3 +152,16 @@ class TestLuauLanguageServer:
|
||||
assert definition["uri"].endswith("init.luau"), f"Definition should be in init.luau, got: {definition['uri']}"
|
||||
# createConfig is defined at line 8 (0-indexed): `local function createConfig(...)`
|
||||
assert definition["range"]["start"]["line"] == 8, f"Definition should be at line 8, got line {definition['range']['start']['line']}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.markdown
|
||||
@@ -93,3 +94,16 @@ class TestMarkdownLanguageServerBasics:
|
||||
|
||||
for symbol in all_symbols:
|
||||
assert symbol["kind"] == SymbolKind.Namespace, f"Nested heading '{symbol['name']}' should be remapped to Namespace"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, whitespace_allowed=True, parenthesis_allowed=True):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -16,6 +16,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Skip all tests if MATLAB is not available
|
||||
pytestmark = pytest.mark.matlab
|
||||
@@ -115,3 +116,16 @@ class TestMatlabLanguageServerReferences:
|
||||
|
||||
# Should find references in both main.m and Calculator.m
|
||||
assert references is not None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -10,7 +10,9 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Skip all Nix tests on Windows as Nix doesn't support Windows
|
||||
pytestmark = pytest.mark.skipif(platform.system() == "Windows", reason="Nix and nil are not available on Windows")
|
||||
@@ -218,3 +220,33 @@ class TestNixLanguageServer:
|
||||
root = symbols[0]
|
||||
assert isinstance(root, dict), "Root should be a dict"
|
||||
assert "name" in root, "Root should have a name"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
# nixd can surface anonymous expression nodes using synthetic display labels
|
||||
# like "(anonymous lambda)", "(dynamic string)", "{anonymous}", and
|
||||
# "(dynamic attribute name)". It also emits literal value nodes such as
|
||||
# strings and arrays. These do not correspond to named declarations, so
|
||||
# there is no bare identifier we could or should normalize them to in the
|
||||
# Nix wrapper. This test is only meant to enforce bare names for symbols
|
||||
# that actually have declaration names.
|
||||
if s["kind"] in {SymbolKind.String, SymbolKind.Array}:
|
||||
continue
|
||||
if s["name"] in {"(anonymous lambda)", "(dynamic string)", "{anonymous}", "(dynamic attribute name)"}:
|
||||
continue
|
||||
if s["kind"] in {SymbolKind.Field, SymbolKind.Property} and "." in s["name"]:
|
||||
continue
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["name"] == "(anonymous lambda)",
|
||||
parenthesis_allowed=s["name"] == "(anonymous lambda)",
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,12 +1,16 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("opam") is None and not is_ci, reason="OPAM is not available")
|
||||
@pytest.mark.ocaml
|
||||
class TestOCamlLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
|
||||
@@ -49,8 +53,6 @@ class TestOCamlLanguageServer:
|
||||
|
||||
def test_reason_file_patterns(self) -> None:
|
||||
"""Test that OCaml language configuration recognizes Reason file extensions"""
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
ocaml_lang = Language.OCAML
|
||||
file_matcher = ocaml_lang.get_source_fn_matcher()
|
||||
|
||||
@@ -150,3 +152,19 @@ class TestOCamlLanguageServer:
|
||||
file_path = os.path.join("bin", "main.ml")
|
||||
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
assert len(symbols) > 0, "Should find symbols in main.ml that use opened modules"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
# happens for lambdas a la let () = ...
|
||||
if s["name"] == "()":
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -10,12 +10,15 @@ Uses genericptr/pascal-language-server which returns SymbolInformation[] format:
|
||||
- Method names don't include parent class prefix; uses containerName instead
|
||||
"""
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import language_tests_enabled
|
||||
from test.conftest import is_ci, language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.pascal,
|
||||
@@ -24,6 +27,7 @@ pytestmark = [
|
||||
|
||||
|
||||
@pytest.mark.pascal
|
||||
@pytest.mark.skipif(shutil.which("fpc") is None and not is_ci, reason="Free Pascal compiler is not available")
|
||||
class TestPascalLanguageServerBasics:
|
||||
"""Test basic functionality of the Pascal language server."""
|
||||
|
||||
@@ -193,3 +197,18 @@ class TestPascalLanguageServerBasics:
|
||||
|
||||
# Should contain the doc comment
|
||||
assert "Calculates the sum" in value, f"Hover should include doc comment. Got: {value[:500]}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Package:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.perl
|
||||
@@ -74,3 +75,16 @@ class TestPerlLanguageServer:
|
||||
main_pl_lines = sorted([ref["range"]["start"]["line"] for ref in main_pl_refs])
|
||||
assert 17 in main_pl_lines, f"Expected reference at line 18 (0-indexed 17), found: {main_pl_lines}"
|
||||
assert 20 in main_pl_lines, f"Expected reference at line 21 (0-indexed 20), found: {main_pl_lines}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -5,10 +6,18 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci, is_windows, language_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _php_supports_phpactor() -> bool:
|
||||
completed_process = subprocess.run(["php", "-m"], capture_output=True, text=True, check=False)
|
||||
installed_extensions = {line.strip().lower() for line in completed_process.stdout.splitlines()}
|
||||
return "openssl" in installed_extensions
|
||||
|
||||
|
||||
_php_servers: list[Language] = [Language.PHP]
|
||||
if language_tests_enabled(Language.PHP_PHPACTOR):
|
||||
if not (is_windows and is_ci): # TODO: Phpactor tests are flaky in Windows CI and can even cause hangs #1040
|
||||
if not (is_windows and is_ci) and _php_supports_phpactor():
|
||||
_php_servers.append(Language.PHP_PHPACTOR)
|
||||
|
||||
|
||||
@@ -256,3 +265,16 @@ class TestPhpLanguageServers:
|
||||
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"
|
||||
|
||||
@pytest.mark.parametrize("language_server", _php_servers, indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -9,6 +9,8 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.powershell
|
||||
@@ -130,6 +132,14 @@ class TestPowerShellLanguageServerBasics:
|
||||
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.ps1, found {len(main_functions)}"
|
||||
assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.ps1, found {len(utils_functions)}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
|
||||
def test_powershell_class_method_symbols_use_bare_method_name(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test whether PSES already reports class methods with bare names."""
|
||||
symbols = language_server.request_full_symbol_tree(within_relative_path="main.ps1")
|
||||
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "PersonFormatter"), "Should find PersonFormatter class in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "FormatName"), "Expected PowerShell method to be exposed with bare name"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
|
||||
def test_powershell_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test finding references to a function within the same file."""
|
||||
@@ -172,3 +182,16 @@ class TestPowerShellLanguageServerBasics:
|
||||
assert any("utils.ps1" in loc.get("uri", "") for loc in definition_locations), (
|
||||
f"Should find definition in utils.ps1, got {definition_locations}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, colon_allowed=s["name"].startswith("$")):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -12,14 +12,14 @@ import pytest
|
||||
from serena.project import Project
|
||||
from serena.util.text_utils import LineType
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES, format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.python
|
||||
class TestPythonLanguageServerBasics:
|
||||
"""Test basic functionality of the language server."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_user_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on the User class."""
|
||||
# Get references to the User class in models.py
|
||||
@@ -34,7 +34,7 @@ class TestPythonLanguageServerBasics:
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
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)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_item_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on the Item class."""
|
||||
# Get references to the Item class in models.py
|
||||
@@ -50,7 +50,7 @@ class TestPythonLanguageServerBasics:
|
||||
services_references = [ref for ref in references if "services.py" in ref["uri"]]
|
||||
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)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_function_parameter(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on a function parameter."""
|
||||
# Get references to the id parameter in get_user method
|
||||
@@ -65,7 +65,7 @@ class TestPythonLanguageServerBasics:
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
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)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_create_user_method(self, language_server: SolidLanguageServer) -> None:
|
||||
# Get references to the create_user method in UserService
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
@@ -81,7 +81,7 @@ class TestPythonLanguageServerBasics:
|
||||
|
||||
|
||||
class TestProjectBasics:
|
||||
@pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_retrieve_content_around_line(self, project: Project) -> None:
|
||||
"""Test retrieve_content_around_line functionality with various scenarios."""
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
@@ -188,7 +188,7 @@ class TestProjectBasics:
|
||||
else:
|
||||
assert line.match_type == LineType.AFTER_MATCH
|
||||
|
||||
@pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_search_files_for_pattern(self, project: Project) -> None:
|
||||
"""Test search_files_for_pattern with various patterns and glob filters."""
|
||||
# Test 1: Search for class definitions across all files
|
||||
@@ -233,3 +233,16 @@ class TestProjectBasics:
|
||||
no_match_pattern = r"def\s+this_method_does_not_exist\s*\([^)]*\):"
|
||||
matches = project.search_source_files_for_pattern(no_match_pattern)
|
||||
assert len(matches) == 0
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import start_ls_context
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
# This mark will be applied to all tests in this module
|
||||
pytestmark = pytest.mark.python
|
||||
@@ -19,7 +20,7 @@ def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
|
||||
yield ls
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that request_full_symbol_tree ignores the configured directory."""
|
||||
root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
|
||||
@@ -28,7 +29,7 @@ def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
assert children_names == {"test_repo", "examples"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that find_references ignores the configured directory."""
|
||||
# Location of Item, which is referenced in scripts
|
||||
@@ -42,7 +43,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
assert not any("scripts" in ref["relativePath"] for ref in references)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("repo_path", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
"""Tests that refs and symbols with glob patterns are ignored."""
|
||||
ignored_paths = ["*ipts", "custom_t*"]
|
||||
|
||||
@@ -12,8 +12,8 @@ import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
pytestmark = pytest.mark.python
|
||||
|
||||
@@ -21,7 +21,7 @@ pytestmark = pytest.mark.python
|
||||
class TestLanguageServerSymbols:
|
||||
"""Test the language server's symbol-related functionality."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
# Test for a position inside the create_user method
|
||||
@@ -36,7 +36,7 @@ class TestLanguageServerSymbols:
|
||||
if "body" in containing_symbol:
|
||||
assert containing_symbol["body"].get_text().strip().startswith("def create_user(self")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_references_to_variables(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a variable."""
|
||||
file_path = os.path.join("test_repo", "variables.py")
|
||||
@@ -51,7 +51,7 @@ class TestLanguageServerSymbols:
|
||||
assert "dataclass_instance" in ref_names
|
||||
assert "second_dataclass" in ref_names
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a class."""
|
||||
# Test for a position inside the UserService class but outside any method
|
||||
@@ -64,7 +64,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["name"] == "UserService"
|
||||
assert containing_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
# Test for a position inside a method which is inside a class
|
||||
@@ -90,7 +90,7 @@ class TestLanguageServerSymbols:
|
||||
assert parent_symbol["name"] == "UserService"
|
||||
assert parent_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a position with no containing symbol."""
|
||||
# Test for a position outside any function/class (e.g., in imports)
|
||||
@@ -101,7 +101,7 @@ class TestLanguageServerSymbols:
|
||||
# Should return None or an empty dictionary
|
||||
assert containing_symbol is None or containing_symbol == {}
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function."""
|
||||
# Test referencing symbols for create_user function
|
||||
@@ -125,7 +125,7 @@ class TestLanguageServerSymbols:
|
||||
assert "start" in symbol["location"]["range"]
|
||||
assert "end" in symbol["location"]["range"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a class."""
|
||||
# Test referencing symbols for User class
|
||||
@@ -146,7 +146,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)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function parameter."""
|
||||
# Test referencing symbols for id parameter in get_user
|
||||
@@ -167,7 +167,7 @@ class TestLanguageServerSymbols:
|
||||
]
|
||||
assert len(method_refs) > 0, "No referencing symbols within method body for get_user (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> 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
|
||||
@@ -185,7 +185,7 @@ class TestLanguageServerSymbols:
|
||||
pass
|
||||
|
||||
# Tests for request_defining_symbol
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a variable usage."""
|
||||
# Test finding the definition of a symbol in the create_user method
|
||||
@@ -204,7 +204,7 @@ class TestLanguageServerSymbols:
|
||||
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
|
||||
assert "services.py" in defining_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for an imported class."""
|
||||
# Test finding the definition of the 'User' class used in the UserService.create_user method
|
||||
@@ -216,7 +216,7 @@ class TestLanguageServerSymbols:
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "User"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a method call."""
|
||||
# Create an example file path for a file that calls UserService.create_user
|
||||
@@ -241,7 +241,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)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a position with no symbol."""
|
||||
# Test for a position with no symbol (e.g., whitespace or comment)
|
||||
@@ -252,7 +252,7 @@ class TestLanguageServerSymbols:
|
||||
# Should return None for positions with no symbol
|
||||
assert defining_symbol is None
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol where the symbol is a variable."""
|
||||
# Test for a position inside a variable definition
|
||||
@@ -265,7 +265,7 @@ class TestLanguageServerSymbols:
|
||||
assert containing_symbol["name"] == "user_var_str"
|
||||
assert containing_symbol["kind"] == SymbolKind.Variable
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a nested function or closure."""
|
||||
# Use the existing nested.py file which contains nested classes and methods
|
||||
@@ -290,15 +290,8 @@ class TestLanguageServerSymbols:
|
||||
# Test 3: Find definition of a method-local function
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 9, 15) # Position inside func_within_func
|
||||
|
||||
# This is challenging for many language servers and may fail
|
||||
try:
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
except (AssertionError, TypeError, KeyError):
|
||||
# This is expected to potentially fail in many implementations
|
||||
import warnings
|
||||
|
||||
warnings.warn("Could not resolve nested class method definition - implementation limitation")
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
|
||||
# Test 2: Find definition of the nested class
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 15, 18) # Position of NestedClass
|
||||
@@ -314,9 +307,9 @@ class TestLanguageServerSymbols:
|
||||
# This is challenging for many language servers and may fail
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
assert defining_symbol.get("kind") == SymbolKind.Function.value
|
||||
assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test the integration between different symbol-related methods."""
|
||||
# This test demonstrates using the various symbol methods together
|
||||
@@ -365,7 +358,7 @@ class TestLanguageServerSymbols:
|
||||
|
||||
warnings.warn("Could not verify container hierarchy - implementation detail")
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
@@ -395,7 +388,7 @@ class TestLanguageServerSymbols:
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
@@ -418,7 +411,7 @@ class TestLanguageServerSymbols:
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that request_dir_overview returns correct symbol information for files in a directory."""
|
||||
# Get overview of the examples directory
|
||||
@@ -442,7 +435,7 @@ class TestLanguageServerSymbols:
|
||||
retrieved_symbols = {symbol["name"] for symbol in services_symbols if "name" in symbol}
|
||||
assert expected_symbols.issubset(retrieved_symbols)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_document_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that request_document_overview returns correct symbol information for a file."""
|
||||
# Get overview of the user_management.py file
|
||||
@@ -451,25 +444,3 @@ class TestLanguageServerSymbols:
|
||||
# Verify that we have entries for both files
|
||||
symbol_names = {LanguageServerSymbol(s_info).name for s_info in overview}
|
||||
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: SolidLanguageServer) -> 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")
|
||||
# import of typing
|
||||
references_to_typing = [
|
||||
ref.symbol
|
||||
for ref in language_server.request_referencing_symbols(file_path, 4, 6, include_imports=False, include_file_symbols=True)
|
||||
]
|
||||
assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File}
|
||||
|
||||
# now include bodies
|
||||
references_to_typing = [
|
||||
ref.symbol
|
||||
for ref in language_server.request_referencing_symbols(
|
||||
file_path, 4, 6, include_imports=False, include_file_symbols=True, include_body=True
|
||||
)
|
||||
]
|
||||
assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File}
|
||||
assert references_to_typing[0]["body"]
|
||||
@@ -3,14 +3,18 @@ Basic tests for R Language Server integration
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("R") is None and not is_ci, reason="R is not available")
|
||||
@pytest.mark.r
|
||||
class TestRLanguageServer:
|
||||
"""Test basic functionality of the R language server."""
|
||||
@@ -82,8 +86,6 @@ class TestRLanguageServer:
|
||||
|
||||
def test_file_matching(self):
|
||||
"""Test that R files are properly matched."""
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
matcher = Language.R.get_source_fn_matcher()
|
||||
|
||||
assert matcher.is_relevant_filename("script.R")
|
||||
@@ -95,3 +97,16 @@ class TestRLanguageServer:
|
||||
"""Test R language enum value."""
|
||||
assert Language.R == "r"
|
||||
assert str(Language.R) == "r"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.R], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -1,14 +1,19 @@
|
||||
"""Tests for Rego language server (Regal) functionality."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("regal") is None and not is_ci, reason="Regal is not available")
|
||||
@pytest.mark.rego
|
||||
class TestRegoLanguageServer:
|
||||
"""Test Regal language server functionality for Rego."""
|
||||
@@ -132,3 +137,18 @@ class TestRegoLanguageServer:
|
||||
assert "validate_user_input" in symbol_names, "validate_user_input rule not found"
|
||||
assert "has_valid_credentials" in symbol_names, "has_valid_credentials function not found"
|
||||
assert "validate_request" in symbol_names, "validate_request rule not found"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Package:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.ruby
|
||||
@@ -37,8 +38,36 @@ class TestRubyLanguageServer:
|
||||
str(repo_path / "main.rb"), 16, 17
|
||||
) # add method at line 17 (0-indexed 16), position 17
|
||||
|
||||
assert len(definition_location_list) == 1
|
||||
definition_location = definition_location_list[0]
|
||||
unique_locations = {
|
||||
(
|
||||
location["relativePath"],
|
||||
location["range"]["start"]["line"],
|
||||
location["range"]["start"]["character"],
|
||||
location["range"]["end"]["line"],
|
||||
location["range"]["end"]["character"],
|
||||
): location
|
||||
for location in definition_location_list
|
||||
}
|
||||
|
||||
assert len(unique_locations) == 1
|
||||
definition_location = next(iter(unique_locations.values()))
|
||||
print(f"Found definition: {definition_location}")
|
||||
assert definition_location["uri"].endswith("lib.rb")
|
||||
assert definition_location["range"]["start"]["line"] == 1 # add method on line 2 (0-indexed 1)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=s["name"] == "<< self",
|
||||
period_allowed=s["name"].startswith("self."),
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.rust
|
||||
@@ -55,3 +56,16 @@ class TestRustLanguageServer:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -13,6 +13,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _find_identifier_position(file_path: Path, symbol_name: str) -> Optional[tuple[int, int]]:
|
||||
@@ -163,3 +164,16 @@ class TestSolidityLanguageServerBasics:
|
||||
|
||||
ref_files = {ref.get("uri", "") for ref in references}
|
||||
assert any("Token.sol" in uri for uri in ref_files), "IERC20.transfer references should include Token.sol"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -15,6 +15,7 @@ from serena.util.text_utils import LineType
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
# Skip Swift tests on Windows due to complex GitHub Actions configuration
|
||||
WINDOWS_SKIP = platform.system() == "Windows"
|
||||
@@ -224,3 +225,16 @@ class TestSwiftProjectBasics:
|
||||
# Should find Status enum
|
||||
status_matches = [m for m in matches if "Status" in str(m)]
|
||||
assert len(status_matches) > 0, "Should find Status enum"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ This module tests Language.SYSTEMVERILOG using verible-verilog-ls.
|
||||
Tests are skipped if the language server is not available.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -12,6 +13,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
|
||||
@@ -262,3 +264,17 @@ class TestSystemVerilogRename:
|
||||
for uri, file_edits in changes.items():
|
||||
for edit in file_edits:
|
||||
assert edit["newText"] == "my_counter", f"Expected 'my_counter', got {edit['newText']}"
|
||||
|
||||
@pytest.mark.skipif(shutil.which("verible-verilog-ls") is None, reason="verible-verilog-ls is not available")
|
||||
@pytest.mark.parametrize("language_server", [Language.SYSTEMVERILOG], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,12 +5,18 @@ These tests validate the functionality of the language server APIs
|
||||
like request_references using the test repository.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import is_ci
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("terraform") is None and not is_ci, reason="Terraform CLI is not available")
|
||||
@pytest.mark.terraform
|
||||
class TestLanguageServerBasics:
|
||||
"""Test basic functionality of the Terraform language server."""
|
||||
@@ -51,3 +57,18 @@ class TestLanguageServerBasics:
|
||||
sel_start = var_symbol["selectionRange"]["start"]
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) >= 1, "variable should be referenced at least once"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if s["kind"] == SymbolKind.Class:
|
||||
continue
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.toml
|
||||
@@ -233,3 +234,16 @@ class TestTomlLanguageServerBasics:
|
||||
strict_symbol = next((s for s in pyproject_symbols if s.get("name") == "strict"), None)
|
||||
if strict_symbol:
|
||||
assert strict_symbol.get("kind") == 17, "'strict' should have kind 17 (boolean)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.typescript
|
||||
@@ -31,3 +32,16 @@ class TestTypescriptLanguageServer:
|
||||
assert any("index.ts" in ref.get("relativePath", "") for ref in refs), (
|
||||
"index.ts should reference helperFunction (tried all positions in selectionRange)"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.vue
|
||||
@@ -360,3 +361,25 @@ class TestVueEdgeCases:
|
||||
f"Composable '{expected_composable}' should appear exactly once in directory overview. "
|
||||
f"Found {len(matching_files)} matches. All files: {list(composables_overview.keys())}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
is_callback = s["name"].endswith(" callback")
|
||||
is_vue_selector = "." in s["name"] or ":" in s["name"]
|
||||
is_vue_block = s["name"] in {"script setup", "style scoped"}
|
||||
if has_malformed_name(
|
||||
s,
|
||||
whitespace_allowed=is_callback or is_vue_selector or is_vue_block,
|
||||
period_allowed=is_vue_selector,
|
||||
colon_allowed=is_vue_selector,
|
||||
parenthesis_allowed=is_callback or is_vue_selector,
|
||||
):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -11,6 +11,7 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.yaml
|
||||
@@ -175,3 +176,16 @@ class TestYAMLLanguageServerBasics:
|
||||
app_port = next((s for s in port_symbols if s["range"]["start"]["line"] == 4), None)
|
||||
assert app_port is not None, "Should find 'port' under 'app'"
|
||||
assert app_port["range"]["start"]["character"] == 2, "'port' should be indented 2 spaces"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -13,6 +13,7 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.zig
|
||||
@@ -341,3 +342,16 @@ class TestZigLanguageServer:
|
||||
root = symbols[0]
|
||||
assert isinstance(root, dict), "Root should be a dict"
|
||||
assert "name" in root, "Root should have a name"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ZIG], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
for s in all_symbols:
|
||||
if has_malformed_name(s, whitespace_allowed=True):
|
||||
malformed_symbols.append(s)
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
Reference in new issue
Block a user