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:
Michael Panchenko authored and GitHub committed 2026-04-01 16:29:00 +02:00
1 parent 7377a84337
commit d30cdf8474
59 files changed
+1015 -97

No files matched your search

+1 -1
View File
@@ -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:
"""
+16 -8
View File
@@ -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"""
+23 -1
View File
@@ -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:
"""
+20 -1
View File
@@ -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:
"""
+1
View File
@@ -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
+2 -2
View File
@@ -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),
],
+7 -7
View File
@@ -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)
+37
View File
@@ -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,
)
+14
View File
@@ -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,
)
+21 -1
View File
@@ -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,
)
+56
View File
@@ -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})"
+14
View File
@@ -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
View File
@@ -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,
)
+14
View File
@@ -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,
)
+28
View File
@@ -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,
)
+17
View File
@@ -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,
)
+14
View File
@@ -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,
)
+14
View File
@@ -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,
)
+29
View File
@@ -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,
)
+14
View File
@@ -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)
+15
View File
@@ -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,
)
+14
View File
@@ -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,
)
+18
View File
@@ -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,
)
+15
View File
@@ -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,
)
+19
View File
@@ -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,
)
+14
View File
@@ -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,
)
+14
View File
@@ -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,
)
+32
View File
@@ -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,
)
+20 -2
View File
@@ -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,
)
+20 -1
View File
@@ -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,
)
+14
View File
@@ -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,
)
+23 -1
View File
@@ -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,
)
+20 -7
View File
@@ -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*"]
+24 -53
View File
@@ -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"]
+17 -2
View File
@@ -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,
)
+20
View File
@@ -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,
)
+31 -2
View File
@@ -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,
)
+14
View File
@@ -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,
)
+14
View File
@@ -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,
)
+14
View File
@@ -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,
)
+23
View File
@@ -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,
)
+14
View File
@@ -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,
)
+14
View File
@@ -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,
)