Refactoring: Rename Language to LanguageServerId

This resolves a long-standing naming issue in Serena & SolidLSP.
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-07-22 20:55:36 +02:00
1 parent ef0e218fb1
commit d2cf18ddcb
180 files changed
+1806 -1767

No files matched your search

@@ -111,12 +111,12 @@ For an example, see `EclipseJDTLS._start_server`.
## Step 2: Language Registration ## Step 2: Language Registration
### 2.1 Add to Language Enum ### 2.1 Add to LanguageServerId Enum
In `src/solidlsp/ls_config.py`, add your language to the `Language` enum: In `src/solidlsp/ls_config.py`, add your language to the enum:
```python ```python
class Language(str, Enum): class LanguageServerId(str, Enum):
# Existing languages... # Existing languages...
NEW_LANGUAGE = "new_language" NEW_LANGUAGE = "new_language"
@@ -125,20 +125,15 @@ class Language(str, Enum):
# Existing cases... # Existing cases...
case self.NEW_LANGUAGE: case self.NEW_LANGUAGE:
return FilenameMatcher(".newlang", ".nl") # File extensions return FilenameMatcher(".newlang", ".nl") # File extensions
```
### 2.2 Update Language Server Factory ...
In `src/solidlsp/ls.py`, add your language to the `create` method: def get_ls_class(self) -> type["SolidLanguageServer"]:
match self:
```python # Existing cases...
@classmethod case self.NEW_LANGUAGE:
def create(cls, config: LanguageServerConfig, repository_root_path: str) -> "SolidLanguageServer": from solidlsp.language_servers.new_language_server import NewLanguageServer
match config.code_language: return NewLanguageServer
# Existing cases...
case Language.NEW_LANGUAGE:
from solidlsp.language_servers.new_language_server import NewLanguageServer
return NewLanguageServer(config, repository_root_path)
``` ```
## Step 3: Test Repository ## Step 3: Test Repository
@@ -214,10 +209,6 @@ You should at least test:
Have a look at `test/solidlsp/php/test_php_basic.py` as an example for what should be tested. Have a look at `test/solidlsp/php/test_php_basic.py` as an example for what should be tested.
Don't forget to add a new language marker to `pytest.ini`. Don't forget to add a new language marker to `pytest.ini`.
### 4.2 Integration Tests
Consider adding new cases to the parametrized tests in `test_serena_agent.py` for the new language.
### 5 Documentation ### 5 Documentation
+2 -2
View File
@@ -23,7 +23,7 @@ from serena.tools import (
GetDiagnosticsForSymbolTool, GetDiagnosticsForSymbolTool,
ReplaceContentTool, ReplaceContentTool,
) )
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
SEPARATOR = "=" * 80 SEPARATOR = "=" * 80
REPO_PATH = Path(REPO_ROOT) REPO_PATH = Path(REPO_ROOT)
@@ -40,7 +40,7 @@ def make_agent() -> SerenaAgent:
project_root=str(REPO_PATH), project_root=str(REPO_PATH),
project_config=ProjectConfig( project_config=ProjectConfig(
project_name="demo_serena_repo", project_name="demo_serena_repo",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
ignored_paths=[], ignored_paths=[],
excluded_tools=[], excluded_tools=[],
read_only=False, read_only=False,
+3 -3
View File
@@ -12,14 +12,14 @@ from serena.config.serena_config import LanguageBackend, ProjectConfig, Register
from serena.constants import REPO_ROOT from serena.constants import REPO_ROOT
from serena.project import Project from serena.project import Project
from serena.tools import FindDeclarationTool from serena.tools import FindDeclarationTool
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
SEPARATOR = "=" * 80 SEPARATOR = "=" * 80
PYTHON_TEST_REPO = Path(REPO_ROOT) / "test" / "resources" / "repos" / "python" / "test_repo" PYTHON_TEST_REPO = Path(REPO_ROOT) / "test" / "resources" / "repos" / "python" / "test_repo"
SERVICES_FILE = Path("test_repo") / "services.py" SERVICES_FILE = Path("test_repo") / "services.py"
def make_agent(project_root: Path, language: Language, project_name: str) -> SerenaAgent: def make_agent(project_root: Path, language: LanguageServerId, project_name: str) -> SerenaAgent:
"""Create an LSP-backed Serena agent for a single explicit project.""" """Create an LSP-backed Serena agent for a single explicit project."""
serena_config = SerenaConfig.from_config_file() serena_config = SerenaConfig.from_config_file()
serena_config.web_dashboard = False serena_config.web_dashboard = False
@@ -64,7 +64,7 @@ def find_identifier_occurrence_position(file_path: Path, identifier: str, occurr
if __name__ == "__main__": if __name__ == "__main__":
agent = make_agent(PYTHON_TEST_REPO, Language.PYTHON, "demo_python_test_repo") agent = make_agent(PYTHON_TEST_REPO, LanguageServerId.PYTHON, "demo_python_test_repo")
try: try:
# letting the language server finish startup # letting the language server finish startup
+3 -3
View File
@@ -11,13 +11,13 @@ from serena.config.serena_config import LanguageBackend, ProjectConfig, Register
from serena.constants import REPO_ROOT from serena.constants import REPO_ROOT
from serena.project import Project from serena.project import Project
from serena.tools import FindImplementationsTool from serena.tools import FindImplementationsTool
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
SEPARATOR = "=" * 80 SEPARATOR = "=" * 80
GO_TEST_REPO = Path(REPO_ROOT) / "test" / "resources" / "repos" / "go" / "test_repo" GO_TEST_REPO = Path(REPO_ROOT) / "test" / "resources" / "repos" / "go" / "test_repo"
def make_agent(project_root: Path, language: Language, project_name: str) -> SerenaAgent: def make_agent(project_root: Path, language: LanguageServerId, project_name: str) -> SerenaAgent:
"""Create an LSP-backed Serena agent for a single explicit project.""" """Create an LSP-backed Serena agent for a single explicit project."""
serena_config = SerenaConfig.from_config_file() serena_config = SerenaConfig.from_config_file()
serena_config.web_dashboard = False serena_config.web_dashboard = False
@@ -49,7 +49,7 @@ def print_section(title: str) -> None:
if __name__ == "__main__": if __name__ == "__main__":
agent = make_agent(GO_TEST_REPO, Language.GO, "demo_go_test_repo") agent = make_agent(GO_TEST_REPO, LanguageServerId.GO, "demo_go_test_repo")
try: try:
# letting the language server finish startup # letting the language server finish startup
+2 -2
View File
@@ -2,10 +2,10 @@
Prints the list of supported languages, for use in the project.yml template Prints the list of supported languages, for use in the project.yml template
""" """
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
if __name__ == "__main__": if __name__ == "__main__":
lang_strings = sorted([l.value for l in Language]) lang_strings = sorted([l.value for l in LanguageServerId])
max_len = max(len(s) for s in lang_strings) max_len = max(len(s) for s in lang_strings)
fmt = f"%-{max_len + 2}s" fmt = f"%-{max_len + 2}s"
for i, l in enumerate(lang_strings): for i, l in enumerate(lang_strings):
+11 -11
View File
@@ -61,7 +61,7 @@ from serena.tools import (
from serena.util.gui import system_has_usable_display from serena.util.gui import system_has_usable_display
from serena.util.inspection import iter_subclasses from serena.util.inspection import iter_subclasses
from serena.util.logging import MemoryLogHandler from serena.util.logging import MemoryLogHandler
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.util import subprocess_util from solidlsp.util import subprocess_util
from solidlsp.util.subprocess_util import terminate_process_tree_with_kill_fallback from solidlsp.util.subprocess_util import terminate_process_tree_with_kill_fallback
@@ -1385,24 +1385,24 @@ class SerenaAgent:
""" """
self.get_active_project_or_raise().create_language_server_manager() self.get_active_project_or_raise().create_language_server_manager()
def add_language(self, language: Language) -> None: def add_language_server(self, ls_id: LanguageServerId) -> None:
""" """
Adds a new language to the active project, spawning the respective language server and updating the project configuration. Adds a new language server to the active project, spawning the respective language server and updating the project configuration.
The addition is scheduled via the agent's task executor and executed synchronously, i.e. the method returns The addition is scheduled via the agent's task executor and executed synchronously, i.e. the method returns
when the addition is complete. when the addition is complete.
:param language: the language to add :param ls_id: the language server to add
""" """
self.execute_task(lambda: self.get_active_project_or_raise().add_language(language), name=f"AddLanguage:{language.value}") self.execute_task(lambda: self.get_active_project_or_raise().add_language_server(ls_id), name=f"AddLanguage:{ls_id.value}")
def remove_language(self, language: Language) -> None: def remove_language_server(self, ls_id: LanguageServerId) -> None:
""" """
Removes a language from the active project, shutting down the respective language server and updating the project configuration. Removes a language server from the active project, shutting down the respective server and updating the project configuration.
The removal is scheduled via the agent's task executor and executed asynchronously. The removal is scheduled via the agent's task executor and executed asynchronously.
:param language: the language to remove :param ls_id: the language to remove
""" """
self.issue_task(lambda: self.get_active_project_or_raise().remove_language(language), name=f"RemoveLanguage:{language.value}") self.issue_task(lambda: self.get_active_project_or_raise().remove_language_server(ls_id), name=f"RemoveLanguage:{ls_id.value}")
def get_tool(self, tool_class: type[TTool]) -> TTool: def get_tool(self, tool_class: type[TTool]) -> TTool:
return self._all_tools[tool_class] return self._all_tools[tool_class]
@@ -1444,11 +1444,11 @@ class SerenaAgent:
tool_class = ToolRegistry().get_tool_class_by_name(tool_name) tool_class = ToolRegistry().get_tool_class_by_name(tool_name)
return self.get_tool(tool_class) return self.get_tool(tool_class)
def get_active_lsp_languages(self) -> list[Language]: def get_active_language_server_ids(self) -> list[LanguageServerId]:
ls_manager = self.get_language_server_manager() ls_manager = self.get_language_server_manager()
if ls_manager is None: if ls_manager is None:
return [] return []
return ls_manager.get_active_languages() return ls_manager.get_active_language_server_ids()
@contextmanager @contextmanager
def active_project_context(self, project: Project) -> Iterator[None]: def active_project_context(self, project: Project) -> Iterator[None]:
+7 -7
View File
@@ -39,7 +39,7 @@ from serena.constants import (
from serena.prompt_factory import SerenaPromptFactory from serena.prompt_factory import SerenaPromptFactory
from serena.util.cli_util import AutoRegisteringGroup from serena.util.cli_util import AutoRegisteringGroup
from serena.util.logging import MemoryLogHandler from serena.util.logging import MemoryLogHandler
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.util.subprocess_util import subprocess_kwargs from solidlsp.util.subprocess_util import subprocess_kwargs
@@ -701,13 +701,13 @@ class ProjectCommands(AutoRegisteringGroup):
if os.path.exists(yml_path): if os.path.exists(yml_path):
raise FileExistsError(f"Project file {yml_path} already exists.") raise FileExistsError(f"Project file {yml_path} already exists.")
languages: list[Language] = [] languages: list[LanguageServerId] = []
if language: if language:
for lang in language: for lang in language:
try: try:
languages.append(Language(lang.lower())) languages.append(LanguageServerId(lang.lower()))
except ValueError: except ValueError:
all_langs = [l.value for l in Language] all_langs = [l.value for l in LanguageServerId]
raise ValueError(f"Unknown language '{lang}'. Supported: {all_langs}") raise ValueError(f"Unknown language '{lang}'. Supported: {all_langs}")
generated_conf = ProjectConfig.autogenerate( generated_conf = ProjectConfig.autogenerate(
@@ -801,13 +801,13 @@ class ProjectCommands(AutoRegisteringGroup):
collected_exceptions: list[Exception] = [] collected_exceptions: list[Exception] = []
files_failed = [] files_failed = []
language_file_counts: dict[Language, int] = collections.defaultdict(lambda: 0) language_file_counts: dict[LanguageServerId, int] = collections.defaultdict(lambda: 0)
last_save_time = time.monotonic() last_save_time = time.monotonic()
for i, f in enumerate(tqdm(files, desc="Indexing")): for i, f in enumerate(tqdm(files, desc="Indexing")):
try: try:
ls = ls_mgr.get_language_server(f) ls = ls_mgr.get_language_server(f)
ls.request_document_symbols(f) ls.request_document_symbols(f)
language_file_counts[ls.language] += 1 language_file_counts[ls.ls_id] += 1
except Exception as e: except Exception as e:
log.error(f"Failed to index {f}, continuing.") log.error(f"Failed to index {f}, continuing.")
collected_exceptions.append(e) collected_exceptions.append(e)
@@ -882,7 +882,7 @@ class ProjectCommands(AutoRegisteringGroup):
ls_mgr = proj.create_language_server_manager() ls_mgr = proj.create_language_server_manager()
try: try:
for ls in ls_mgr.iter_language_servers(): for ls in ls_mgr.iter_language_servers():
click.echo(f"Indexing for language {ls.language.value} …") click.echo(f"Indexing for language {ls.ls_id.value} …")
document_symbols = ls.request_document_symbols(file) document_symbols = ls.request_document_symbols(file)
symbols, _ = document_symbols.get_all_symbols_and_roots() symbols, _ = document_symbols.get_all_symbols_and_roots()
if verbose: if verbose:
+41 -40
View File
@@ -32,10 +32,10 @@ from serena.constants import (
SERENA_FILE_ENCODING, SERENA_FILE_ENCODING,
SERENA_MANAGED_DIR_NAME, SERENA_MANAGED_DIR_NAME,
) )
from serena.util.inspection import determine_programming_language_composition from serena.util.inspection import compute_language_server_support_composition
from serena.util.text_utils import glob_match from serena.util.text_utils import glob_match
from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_yaml_comments from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_yaml_comments
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from ..analytics import RegisteredTokenCountEstimator from ..analytics import RegisteredTokenCountEstimator
from ..util.class_decorators import singleton from ..util.class_decorators import singleton
@@ -321,7 +321,7 @@ class ProjectConfigAutoGenerationMode(Enum):
@dataclass(kw_only=True) @dataclass(kw_only=True)
class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes): class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
project_name: str project_name: str
languages: list[Language] languages: list[LanguageServerId]
ignored_paths: list[str] = field(default_factory=list) ignored_paths: list[str] = field(default_factory=list)
ls_workspace_folders: list[str] = field(default_factory=lambda: ["."]) ls_workspace_folders: list[str] = field(default_factory=lambda: ["."])
ls_additional_workspace_folders: list[str] = field(default_factory=list) ls_additional_workspace_folders: list[str] = field(default_factory=list)
@@ -356,57 +356,58 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
return ["project_name"] return ["project_name"]
@classmethod @classmethod
def _determine_project_languages(cls, project_root: str, interactive: bool, serena_config: "SerenaConfig") -> list[Language]: def _determine_project_language_servers(
log.info("Determining programming languages used in the project") cls, project_root: str, interactive: bool, serena_config: "SerenaConfig"
) -> list[LanguageServerId]:
log.info("Determining suitable language servers for the project")
# determine languages to be considered and their priorities # determine language servers to be considered and their priorities
language_priorities = {} ls_priorities = {}
for language in Language: for language in LanguageServerId:
priority = serena_config.get_ls_priority(language) priority = serena_config.get_ls_priority(language)
if priority > 0: if priority > 0:
language_priorities[language] = priority ls_priorities[language] = priority
log.debug("Language priorities: %s", language_priorities) log.debug("Language server priorities: %s", ls_priorities)
language_composition = determine_programming_language_composition(project_root, list(language_priorities.keys())) ls_composition = compute_language_server_support_composition(project_root, list(ls_priorities.keys()))
log.info("Project language composition: %s", language_composition) log.info("Project composition: %s", ls_composition)
if len(language_composition) == 0: if len(ls_composition) == 0:
log.warning( log.warning(
"No source files for supported language servers were found in %s. " "No source files for supported language servers were found in %s. "
"Creating project with no configured languages. " "Creating project with no configured language servers. "
"Symbol-related tools (e.g. find_symbol, get_symbols_overview) will not work " "Symbol-related tools (e.g. find_symbol, get_symbols_overview) will not work "
"when using the LSP backend. You can add languages later via the Serena dashboard " "when using the LSP backend. You can add languages later via the Serena dashboard "
"or by manually editing the project configuration.", "or by manually editing the project configuration.",
project_root, project_root,
) )
languages_to_use = [] language_servers_to_use = []
else: else:
# sort languages by number of files found # sort languages by number of files found
languages_and_percentages = sorted( languages_and_percentages = sorted(ls_composition.items(), key=lambda item: (item[1], ls_priorities[item[0]]), reverse=True)
language_composition.items(), key=lambda item: (item[1], language_priorities[item[0]]), reverse=True
)
# find the language with the highest percentage and enable it # find the language with the highest percentage and enable it
top_language_pair = languages_and_percentages[0] top_language_pair = languages_and_percentages[0]
other_language_pairs = languages_and_percentages[1:] other_language_pairs = languages_and_percentages[1:]
languages_to_use = [top_language_pair[0]] language_servers_to_use = [top_language_pair[0]]
# if in interactive mode, ask the user which other languages to enable # if in interactive mode, ask the user which other languages to enable
if len(other_language_pairs) > 0 and interactive: if len(other_language_pairs) > 0 and interactive:
print( print(
"Detected and enabled main language '%s' (%.2f%% of source files)." % (top_language_pair[0].value, top_language_pair[1]) "Detected and enabled main language server '%s' (%.2f%% of source files)."
% (top_language_pair[0].value, top_language_pair[1])
) )
print(f"Additionally detected {len(other_language_pairs)} other language(s).\n") print(f"Additionally detected {len(other_language_pairs)} other applicable language servers.\n")
print("Note: Enable only languages you need symbolic retrieval/editing capabilities for.") print("Note: Enable only servers for languages you need symbolic retrieval/editing capabilities for.")
print(" Additional language servers use resources and some languages may require additional") print(" Additional language servers use resources and some may require additional")
print(" system-level installations/configuration (see Serena documentation).") print(" system-level installations/configuration (see Serena documentation).")
print("\nWhich additional languages do you want to enable?") print("\nWhich additional language servers do you want to enable?")
for lang, perc in other_language_pairs: for ls_id, perc in other_language_pairs:
enable = ask_yes_no("Enable %s (%.2f%% of source files)?" % (lang.value, perc), default=False) enable = ask_yes_no("Enable %s (%.2f%% of source files)?" % (ls_id.value, perc), default=False)
if enable: if enable:
languages_to_use.append(lang) language_servers_to_use.append(ls_id)
print() print()
log.info("Using languages: %s", languages_to_use) log.info("Using language servers: %s", language_servers_to_use)
return languages_to_use return language_servers_to_use
@classmethod @classmethod
def autogenerate( def autogenerate(
@@ -414,7 +415,7 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
project_root: str | Path, project_root: str | Path,
serena_config: "SerenaConfig", serena_config: "SerenaConfig",
project_name: str | None = None, project_name: str | None = None,
languages: list[Language] | None = None, languages: list[LanguageServerId] | None = None,
save_to_disk: bool = True, save_to_disk: bool = True,
interactive: bool = False, interactive: bool = False,
asynchronous: bool = False, asynchronous: bool = False,
@@ -448,7 +449,7 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
use_asynchronous_language_determination = True use_asynchronous_language_determination = True
languages_to_use = [] # temporarily empty, will be determined in background thread languages_to_use = [] # temporarily empty, will be determined in background thread
else: else:
determined_languages = cls._determine_project_languages( determined_languages = cls._determine_project_language_servers(
str(project_root), interactive=interactive, serena_config=serena_config str(project_root), interactive=interactive, serena_config=serena_config
) )
languages_to_use = [l.value for l in determined_languages] languages_to_use = [l.value for l in determined_languages]
@@ -477,7 +478,7 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
def async_language_determination(): def async_language_determination():
try: try:
with LogTime("Asynchronous language determination", logger=log): with LogTime("Asynchronous language determination", logger=log):
project_config.languages = cls._determine_project_languages( project_config.languages = cls._determine_project_language_servers(
str(project_root), interactive=False, serena_config=serena_config str(project_root), interactive=False, serena_config=serena_config
) )
if save_to_disk: if save_to_disk:
@@ -579,18 +580,18 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
""" """
# map languages to list of enum items, checking for errors # map languages to list of enum items, checking for errors
lang_name_mapping = {"javascript": "typescript"} lang_name_mapping = {"javascript": "typescript"}
languages: list[Language] = [] languages: list[LanguageServerId] = []
for language_str in data["languages"]: for language_str in data["languages"]:
orig_language_str = language_str orig_language_str = language_str
try: try:
language_str = language_str.lower() language_str = language_str.lower()
if language_str in lang_name_mapping: if language_str in lang_name_mapping:
language_str = lang_name_mapping[language_str] language_str = lang_name_mapping[language_str]
language = Language(language_str) language = LanguageServerId(language_str)
languages.append(language) languages.append(language)
except ValueError as e: except ValueError as e:
raise ValueError( raise ValueError(
f"Invalid language: {orig_language_str}.\nValid language_strings are: {[l.value for l in Language]}" f"Invalid language: {orig_language_str}.\nValid language_strings are: {[l.value for l in LanguageServerId]}"
) from e ) from e
# Validate activation_command_timeout # Validate activation_command_timeout
@@ -1451,18 +1452,18 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
log.info(f"Using language backend from global configuration: {language_backend.name}") log.info(f"Using language backend from global configuration: {language_backend.name}")
return language_backend return language_backend
def get_ls_priority(self, language: Language) -> int: def get_ls_priority(self, ls_id: LanguageServerId) -> int:
""" """
Gets the priority value associated with a language server Gets the priority value associated with a language server
:param language: identifies the language server :param ls_id: identifies the language server
:return: the integer priority :return: the integer priority
""" """
if self.ls_priorities is not None: if self.ls_priorities is not None:
try: try:
configured_value = self.ls_priorities.get(language.value) configured_value = self.ls_priorities.get(ls_id.value)
if configured_value is not None: if configured_value is not None:
return int(configured_value) return int(configured_value)
except Exception as e: except Exception as e:
log.error("Error reading language priority for %s: %s. Using default priority.", language.value, e) log.error("Error reading language priority for %s: %s. Using default priority.", ls_id.value, e)
return language.get_priority() return ls_id.get_priority()
+10 -10
View File
@@ -638,10 +638,10 @@ class SerenaDashboardAPI:
self._current_config_overview = self._compute_config_overview().model_dump() self._current_config_overview = self._compute_config_overview().model_dump()
def _get_available_languages(self) -> ResponseAvailableLanguages: def _get_available_languages(self) -> ResponseAvailableLanguages:
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
def run() -> ResponseAvailableLanguages: def run() -> ResponseAvailableLanguages:
all_languages = [lang.value for lang in Language.iter_all(include_experimental=True)] all_languages = [lang.value for lang in LanguageServerId.iter_all(include_experimental=True)]
# Filter out already added languages for the active project # Filter out already added languages for the active project
project = self._agent.get_active_project() project = self._agent.get_active_project()
@@ -776,24 +776,24 @@ class SerenaDashboardAPI:
return {} return {}
def _add_language(self, request_add_language: RequestAddLanguage) -> None: def _add_language(self, request_add_language: RequestAddLanguage) -> None:
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
try: try:
language = Language(request_add_language.language) language = LanguageServerId(request_add_language.language)
except ValueError: except ValueError:
raise ValueError(f"Invalid language: {request_add_language.language}") raise ValueError(f"Invalid language server identifier: {request_add_language.language}")
# add_language is already thread-safe # add_language is already thread-safe
self._agent.add_language(language) self._agent.add_language_server(language)
def _remove_language(self, request_remove_language: RequestRemoveLanguage) -> None: def _remove_language(self, request_remove_language: RequestRemoveLanguage) -> None:
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
try: try:
language = Language(request_remove_language.language) language = LanguageServerId(request_remove_language.language)
except ValueError: except ValueError:
raise ValueError(f"Invalid language: {request_remove_language.language}") raise ValueError(f"Invalid language server identifier: {request_remove_language.language}")
# remove_language is already thread-safe # remove_language is already thread-safe
self._agent.remove_language(language) self._agent.remove_language_server(language)
@staticmethod @staticmethod
def _find_first_free_port(start_port: int, host: str) -> int: def _find_first_free_port(start_port: int, host: str) -> int:
+35 -33
View File
@@ -9,7 +9,7 @@ from sensai.util.logging import LogTime
from serena.config.serena_config import ProjectConfig, SerenaPaths from serena.config.serena_config import ProjectConfig, SerenaPaths
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.lsp_protocol_handler.lsp_types import DidChangeWatchedFilesParams, FileChangeType, FileEvent from solidlsp.lsp_protocol_handler.lsp_types import DidChangeWatchedFilesParams, FileChangeType, FileEvent
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -45,17 +45,17 @@ class LanguageServerFactory:
self.ls_specific_settings = ls_specific_settings self.ls_specific_settings = ls_specific_settings
self.trace_lsp_communication = trace_lsp_communication self.trace_lsp_communication = trace_lsp_communication
def create_language_server(self, language: Language) -> SolidLanguageServer: def create_language_server(self, ls_id: LanguageServerId) -> SolidLanguageServer:
ls_config = LanguageServerConfig( ls_config = LanguageServerConfig(
workspace_folders=self.project_config.ls_workspace_folders, workspace_folders=self.project_config.ls_workspace_folders,
additional_workspace_folders=self.project_config.ls_additional_workspace_folders, additional_workspace_folders=self.project_config.ls_additional_workspace_folders,
code_language=language, ls_id=ls_id,
ignored_paths=self.ignored_patterns, ignored_paths=self.ignored_patterns,
trace_lsp_communication=self.trace_lsp_communication, trace_lsp_communication=self.trace_lsp_communication,
encoding=self.encoding, encoding=self.encoding,
) )
log.info(f"Creating language server instance for {self.project_root}, language={language}.") log.info(f"Creating language server instance for {self.project_root}, language={ls_id}.")
return SolidLanguageServer.create( return SolidLanguageServer.create(
ls_config, ls_config,
self.project_root, self.project_root,
@@ -74,7 +74,10 @@ class LanguageServerManager:
""" """
def __init__( def __init__(
self, language_servers: dict[Language, SolidLanguageServer], language_server_factory: LanguageServerFactory, project: "Project" self,
language_servers: dict[LanguageServerId, SolidLanguageServer],
language_server_factory: LanguageServerFactory,
project: "Project",
) -> None: ) -> None:
""" """
:param language_servers: a mapping from language to language server; the servers are assumed to be already started. :param language_servers: a mapping from language to language server; the servers are assumed to be already started.
@@ -94,7 +97,7 @@ class LanguageServerManager:
return next(iter(self._language_servers.values())) return next(iter(self._language_servers.values()))
@staticmethod @staticmethod
def from_languages(languages: list[Language], factory: LanguageServerFactory, project: "Project") -> "LanguageServerManager": def from_languages(languages: list[LanguageServerId], factory: LanguageServerFactory, project: "Project") -> "LanguageServerManager":
""" """
Creates a manager with language servers for the given languages using the given factory. Creates a manager with language servers for the given languages using the given factory.
The language servers are started in parallel threads. The language servers are started in parallel threads.
@@ -106,21 +109,21 @@ class LanguageServerManager:
""" """
class StartLSThread(threading.Thread): class StartLSThread(threading.Thread):
def __init__(self, language: Language): def __init__(self, ls_id: LanguageServerId):
super().__init__(target=self._start_language_server, name="StartLS:" + language.value) super().__init__(target=self._start_language_server, name="StartLS:" + ls_id.value)
self.language = language self.ls_id = ls_id
self.language_server: SolidLanguageServer | None = None self.language_server: SolidLanguageServer | None = None
self.exception: Exception | None = None self.exception: Exception | None = None
def _start_language_server(self) -> None: def _start_language_server(self) -> None:
try: try:
with LogTime(f"Language server startup (language={self.language.value})"): with LogTime(f"Language server startup (language={self.ls_id.value})"):
self.language_server = factory.create_language_server(self.language) self.language_server = factory.create_language_server(self.ls_id)
self.language_server.start() self.language_server.start()
if not self.language_server.is_running(): if not self.language_server.is_running():
raise RuntimeError(f"Failed to start the language server for language {self.language.value}") raise RuntimeError(f"Failed to start the language server for language {self.ls_id.value}")
except Exception as e: except Exception as e:
log.error(f"Error starting language server for language {self.language.value}: {e}", exc_info=e) log.error(f"Error starting language server for language {self.ls_id.value}: {e}", exc_info=e)
self.exception = e self.exception = e
# start language servers in parallel threads # start language servers in parallel threads
@@ -131,14 +134,14 @@ class LanguageServerManager:
threads.append(thread) threads.append(thread)
# collect language servers and exceptions # collect language servers and exceptions
language_servers: dict[Language, SolidLanguageServer] = {} language_servers: dict[LanguageServerId, SolidLanguageServer] = {}
exceptions: dict[Language, Exception] = {} exceptions: dict[LanguageServerId, Exception] = {}
for thread in threads: for thread in threads:
thread.join() thread.join()
if thread.exception is not None: if thread.exception is not None:
exceptions[thread.language] = thread.exception exceptions[thread.ls_id] = thread.exception
elif thread.language_server is not None: elif thread.language_server is not None:
language_servers[thread.language] = thread.language_server language_servers[thread.ls_id] = thread.language_server
# If any server failed to start up, raise an exception and stop all started language servers. # If any server failed to start up, raise an exception and stop all started language servers.
# We intentionally fail fast here. The user's intention is to work with all the specified languages, # We intentionally fail fast here. The user's intention is to work with all the specified languages,
@@ -155,8 +158,8 @@ class LanguageServerManager:
def _ensure_functional_ls(self, ls: SolidLanguageServer) -> SolidLanguageServer: def _ensure_functional_ls(self, ls: SolidLanguageServer) -> SolidLanguageServer:
if not ls.is_running(): if not ls.is_running():
log.warning(f"Language server for language {ls.language} is not running; restarting ...") log.warning(f"Language server for language {ls.ls_id} is not running; restarting ...")
ls = self.restart_language_server(ls.language) ls = self.restart_language_server(ls.ls_id)
return ls return ls
def _get_suitable_language_server(self, relative_path: str) -> SolidLanguageServer | None: def _get_suitable_language_server(self, relative_path: str) -> SolidLanguageServer | None:
@@ -177,15 +180,15 @@ class LanguageServerManager:
ls = self._default_language_server ls = self._default_language_server
return self._ensure_functional_ls(ls) return self._ensure_functional_ls(ls)
def _create_and_start_language_server(self, language: Language) -> SolidLanguageServer: def _create_and_start_language_server(self, ls_id: LanguageServerId) -> SolidLanguageServer:
if self._language_server_factory is None: if self._language_server_factory is None:
raise ValueError(f"No language server factory available to create language server for {language}") raise ValueError(f"No language server factory available to create language server for {ls_id}")
language_server = self._language_server_factory.create_language_server(language) language_server = self._language_server_factory.create_language_server(ls_id)
language_server.start() language_server.start()
self._language_servers[language] = language_server self._language_servers[ls_id] = language_server
return language_server return language_server
def restart_language_server(self, language: Language) -> SolidLanguageServer: def restart_language_server(self, language: LanguageServerId) -> SolidLanguageServer:
""" """
Forces recreation and restart of the language server for the given language. Forces recreation and restart of the language server for the given language.
It is assumed that the language server for the given language is no longer running. It is assumed that the language server for the given language is no longer running.
@@ -197,19 +200,18 @@ class LanguageServerManager:
raise ValueError(f"No language server for language {language.value} present; cannot restart") raise ValueError(f"No language server for language {language.value} present; cannot restart")
return self._create_and_start_language_server(language) return self._create_and_start_language_server(language)
def add_language_server(self, language: Language) -> SolidLanguageServer: def add_language_server(self, ls_id: LanguageServerId) -> SolidLanguageServer:
""" """
Dynamically adds a new language server for the given language. Dynamically adds a new language server for the given language.
:param language: the language :param ls_id: the language server to add
:param factory: the factory to create the language server
:return: the newly created language server :return: the newly created language server
""" """
if language in self._language_servers: if ls_id in self._language_servers:
raise ValueError(f"Language server for language {language.value} already present") raise ValueError(f"Language server for language {ls_id.value} already present")
return self._create_and_start_language_server(language) return self._create_and_start_language_server(ls_id)
def remove_language_server(self, language: Language, save_cache: bool = False) -> None: def remove_language_server(self, language: LanguageServerId, save_cache: bool = False) -> None:
""" """
Removes the language server for the given language, stopping it if it is running. Removes the language server for the given language, stopping it if it is running.
@@ -220,7 +222,7 @@ class LanguageServerManager:
ls = self._language_servers.pop(language) ls = self._language_servers.pop(language)
self._stop_language_server(ls, save_cache=save_cache) self._stop_language_server(ls, save_cache=save_cache)
def get_active_languages(self) -> list[Language]: def get_active_language_server_ids(self) -> list[LanguageServerId]:
""" """
Returns the list of languages for which language servers are currently managed. Returns the list of languages for which language servers are currently managed.
@@ -233,7 +235,7 @@ class LanguageServerManager:
if ls.is_running(): if ls.is_running():
if save_cache: if save_cache:
ls.save_cache() ls.save_cache()
log.info(f"Stopping language server for language {ls.language} ...") log.info(f"Stopping language server for language {ls.ls_id} ...")
ls.stop(shutdown_timeout=timeout) ls.stop(shutdown_timeout=timeout)
def iter_language_servers(self) -> Iterator[SolidLanguageServer]: def iter_language_servers(self) -> Iterator[SolidLanguageServer]:
+20 -20
View File
@@ -20,7 +20,7 @@ from serena.util.file_proxy import FileCollection, FileProxy
from serena.util.file_system import GitignoreParser, match_path, scan_directory from serena.util.file_system import GitignoreParser, match_path, scan_directory
from serena.util.text_utils import MatchedConsecutiveLines, search_files from serena.util.text_utils import MatchedConsecutiveLines, search_files
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
if TYPE_CHECKING: if TYPE_CHECKING:
from serena.agent import SerenaAgent from serena.agent import SerenaAgent
@@ -493,7 +493,7 @@ class Project(ToStringMixin):
ls_specific_settings=ls_specific_settings, ls_specific_settings=ls_specific_settings,
trace_lsp_communication=self.serena_config.trace_lsp_communication, trace_lsp_communication=self.serena_config.trace_lsp_communication,
) )
self.language_server_manager = LanguageServerManager.from_languages(self.project_config.languages, factory, self) self.language_server_manager = LanguageServerManager.from_languages(self.project_config.language_servers, factory, self)
return self.language_server_manager return self.language_server_manager
except Exception as e: except Exception as e:
self._language_server_manager_init_error = e self._language_server_manager_init_error = e
@@ -512,50 +512,50 @@ class Project(ToStringMixin):
raise Exception(msg.build()) raise Exception(msg.build())
return self.language_server_manager return self.language_server_manager
def add_language(self, language: Language) -> None: def add_language_server(self, ls_id: LanguageServerId) -> None:
""" """
Adds a new programming language to the project configuration, starting the corresponding Adds a new language server to the project configuration, starting the corresponding
language server instance if the LS manager is active. server instance if the LS manager is active.
The project configuration is saved to disk after adding the language. The project configuration is saved to disk after adding the language.
:param language: the programming language to add :param ls_id: the language server to add
""" """
if language in self.project_config.languages: if ls_id in self.project_config.language_servers:
log.info(f"Language {language.value} is already present in the project configuration.") log.info(f"Language server {ls_id.value} is already present in the project configuration.")
return return
# start the language server (if the LS manager is active) # start the language server (if the LS manager is active)
if self.language_server_manager is None: if self.language_server_manager is None:
log.info("Language server manager is not active; skipping language server startup for the new language.") log.info("Language server manager is not active; skipping language server startup for the new language.")
else: else:
log.info("Adding and starting the language server for new language %s ...", language.value) log.info("Adding and starting the language server '%s' ...", ls_id.value)
self.language_server_manager.add_language_server(language) self.language_server_manager.add_language_server(ls_id)
# update the project configuration # update the project configuration
self.project_config.languages.append(language) self.project_config.language_servers.append(ls_id)
self.save_config() self.save_config()
def remove_language(self, language: Language) -> None: def remove_language_server(self, ls_id: LanguageServerId) -> None:
""" """
Removes a programming language from the project configuration, stopping the corresponding Removes a language server from the project configuration, stopping the corresponding
language server instance if the LS manager is active. server instance if the LS manager is active.
The project configuration is saved to disk after removing the language. The project configuration is saved to disk after removing the language.
:param language: the programming language to remove :param ls_id: the language server to remove
""" """
if language not in self.project_config.languages: if ls_id not in self.project_config.language_servers:
log.info(f"Language {language.value} is not present in the project configuration.") log.info(f"Language {ls_id.value} is not present in the project configuration.")
return return
# update the project configuration # update the project configuration
self.project_config.languages.remove(language) self.project_config.language_servers.remove(ls_id)
self.save_config() self.save_config()
# stop the language server (if the LS manager is active) # stop the language server (if the LS manager is active)
if self.language_server_manager is None: if self.language_server_manager is None:
log.info("Language server manager is not active; skipping language server shutdown for the removed language.") log.info("Language server manager is not active; skipping language server shutdown for the removed language.")
else: else:
log.info("Removing and stopping the language server for language %s ...", language.value) log.info("Removing and stopping the language server for language %s ...", ls_id.value)
self.language_server_manager.remove_language_server(language) self.language_server_manager.remove_language_server(ls_id)
def ls_sync_file_system_changes(self) -> int: def ls_sync_file_system_changes(self) -> int:
""" """
+1 -1
View File
@@ -107,7 +107,7 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead):
raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ") raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ")
if not symbol_retriever.can_analyze_file(relative_path): if not symbol_retriever.can_analyze_file(relative_path):
raise ValueError( raise ValueError(
f"Cannot extract symbols from file {relative_path}. Active languages: {[l.value for l in self.agent.get_active_lsp_languages()]}" f"Cannot extract symbols from file {relative_path}. Active language servers: {[l.value for l in self.agent.get_active_language_server_ids()]}"
) )
symbols = symbol_retriever.get_symbol_overview(relative_path)[relative_path] symbols = symbol_retriever.get_symbol_overview(relative_path)[relative_path]
+16 -14
View File
@@ -4,7 +4,7 @@ from collections.abc import Callable, Iterator
from typing import TypeVar from typing import TypeVar
from serena.util.file_system import find_all_non_ignored_files from serena.util.file_system import find_all_non_ignored_files
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
T = TypeVar("T") T = TypeVar("T")
@@ -27,40 +27,42 @@ def iter_subclasses(
yield from iter_subclasses(subclass, recursive, inclusion_predicate) yield from iter_subclasses(subclass, recursive, inclusion_predicate)
def determine_programming_language_composition(repo_path: str, languages: list[Language] | None = None) -> dict[Language, float]: def compute_language_server_support_composition(
repo_path: str, ls_ids: list[LanguageServerId] | None = None
) -> dict[LanguageServerId, float]:
""" """
Determine the programming language composition of a repository. Determine the composition of a repository in terms of the language servers that can be used to analyze it.
Percentages are computed relative to the number of files that match at least Percentages are computed relative to the number of files that match at least
one supported language, not the total file count. This prevents files that one supported language server, not the total file count. This prevents files that
belong to no supported language (images, plain text, licenses, lock files, etc.) belong to no supported language (images, plain text, licenses, lock files, etc.)
from diluting language percentages in repositories where such files dominate. from diluting language percentages in repositories where such files dominate.
:param repo_path: path to the repository to analyze :param repo_path: path to the repository to analyze
:param languages: the list of languages to consider; if None, use default languages :param ls_ids: the list of language servers to consider; if None, use default (non-experimental ones)
:return: dictionary mapping languages to percentages of recognised source files :return: dictionary mapping language servers to percentages of recognised source files
matching the respective language (denominator = files matched by at least one language) (denominator = files matched by at least one language server)
""" """
if languages is None: if ls_ids is None:
languages = list(Language.iter_all(include_experimental=False)) ls_ids = list(LanguageServerId.iter_all(include_experimental=False))
all_files = find_all_non_ignored_files(repo_path) all_files = find_all_non_ignored_files(repo_path)
if not all_files: if not all_files:
return {} return {}
matchers = {lang: lang.get_source_fn_matcher() for lang in languages} matchers = {lang: lang.get_source_fn_matcher() for lang in ls_ids}
# count files per language in a single pass over the files # count files per language in a single pass over the files
language_counts: dict[Language, int] = {} ls_file_counts: dict[LanguageServerId, int] = {}
recognised_files = 0 recognised_files = 0
for file_path in all_files: for file_path in all_files:
# Use just the filename for matching, not the full path # Use just the filename for matching, not the full path
filename = os.path.basename(file_path) filename = os.path.basename(file_path)
matched_any = False matched_any = False
for lang, matcher in matchers.items(): for ls_id, matcher in matchers.items():
if matcher.is_relevant_filename(filename): if matcher.is_relevant_filename(filename):
language_counts[lang] = language_counts.get(lang, 0) + 1 ls_file_counts[ls_id] = ls_file_counts.get(ls_id, 0) + 1
matched_any = True matched_any = True
if matched_any: if matched_any:
recognised_files += 1 recognised_files += 1
@@ -69,4 +71,4 @@ def determine_programming_language_composition(repo_path: str, languages: list[L
return {} return {}
# convert to percentages relative to recognised source files only # convert to percentages relative to recognised source files only
return {lang: round(count / recognised_files * 100, 2) for lang, count in language_counts.items()} return {ls_id: round(count / recognised_files * 100, 2) for ls_id, count in ls_file_counts.items()}
@@ -18,7 +18,7 @@ from overrides import override
from solidlsp import ls_types from solidlsp import ls_types
from solidlsp.language_servers.common import quote_windows_path from solidlsp.language_servers.common import quote_windows_path
from solidlsp.ls import DocumentSymbols, LSPFileBuffer, RawDocumentSymbol, SolidLanguageServer from solidlsp.ls import DocumentSymbols, LSPFileBuffer, RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
from solidlsp.ls_utils import FileUtils from solidlsp.ls_utils import FileUtils
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink
@@ -240,7 +240,7 @@ class ALLanguageServer(SolidLanguageServer):
log.warning(f"AL_EXTENSION_PATH set but directory not found: {env_path}") log.warning(f"AL_EXTENSION_PATH set but directory not found: {env_path}")
# Check the resolved-version download location (versioned for non-INITIAL, legacy "al-extension" for INITIAL) # Check the resolved-version download location (versioned for non-INITIAL, legacy "al-extension" for INITIAL)
al_settings = solidlsp_settings.get_ls_specific_settings(Language.AL) al_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.AL)
al_extension_version = al_settings.get("al_extension_version", DEFAULT_AL_EXTENSION_VERSION) al_extension_version = al_settings.get("al_extension_version", DEFAULT_AL_EXTENSION_VERSION)
default_path = os.path.join(cls.ls_resources_dir(solidlsp_settings), _al_extension_dirname(al_extension_version), "extension") default_path = os.path.join(cls.ls_resources_dir(solidlsp_settings), _al_extension_dirname(al_extension_version), "extension")
if os.path.exists(default_path): if os.path.exists(default_path):
@@ -265,7 +265,7 @@ class ALLanguageServer(SolidLanguageServer):
Path to installed extension or None if download failed Path to installed extension or None if download failed
""" """
al_settings = solidlsp_settings.get_ls_specific_settings(Language.AL) al_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.AL)
al_extension_version = al_settings.get("al_extension_version", DEFAULT_AL_EXTENSION_VERSION) al_extension_version = al_settings.get("al_extension_version", DEFAULT_AL_EXTENSION_VERSION)
al_extension_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), _al_extension_dirname(al_extension_version)) al_extension_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), _al_extension_dirname(al_extension_version))
al_extension_url = ( al_extension_url = (
@@ -69,7 +69,7 @@ from solidlsp.language_servers.typescript_language_server import (
) )
from solidlsp.language_servers.vscode_html_language_server import VsCodeHtmlLanguageServer from solidlsp.language_servers.vscode_html_language_server import VsCodeHtmlLanguageServer
from solidlsp.ls import LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer from solidlsp.ls import LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig from solidlsp.ls_config import FilenameMatcher, LanguageServerConfig, LanguageServerId
from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, SymbolInformation from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, SymbolInformation
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -97,13 +97,13 @@ class AngularTypeScriptServer(TypeScriptLanguageServer):
@classmethod @classmethod
@override @override
def get_language_enum_instance(cls) -> Language: def get_language_server_id(cls) -> LanguageServerId:
return Language.TYPESCRIPT return LanguageServerId.TYPESCRIPT
def get_source_fn_matcher(self) -> FilenameMatcher: def get_source_fn_matcher(self) -> FilenameMatcher:
# Use the Angular matcher so .html template files aren't filtered out of # Use the Angular matcher so .html template files aren't filtered out of
# reference / search results when the companion is asked about them. # reference / search results when the companion is asked about them.
return Language.ANGULAR.get_source_fn_matcher() return LanguageServerId.ANGULAR.get_source_fn_matcher()
class DependencyProvider(TypeScriptLanguageServer.DependencyProvider): class DependencyProvider(TypeScriptLanguageServer.DependencyProvider):
"""Dependency provider that returns a pre-resolved executable path. """Dependency provider that returns a pre-resolved executable path.
@@ -293,8 +293,8 @@ class AngularLanguageServer(SolidLanguageServer):
assert shutil.which("node") is not None, "node is not installed or isn't in PATH. Please install NodeJS and try again." assert shutil.which("node") is not None, "node is not installed or isn't in PATH. Please install NodeJS and try again."
assert shutil.which("npm") is not None, "npm is not installed or isn't in PATH. Please install npm and try again." assert shutil.which("npm") is not None, "npm is not installed or isn't in PATH. Please install npm and try again."
ng_settings = solidlsp_settings.get_ls_specific_settings(Language.ANGULAR) ng_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.ANGULAR)
ts_settings = solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT) ts_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.TYPESCRIPT)
ls_version = ng_settings.get("angular_language_server_version", DEFAULT_ANGULAR_LANGUAGE_SERVER_VERSION) ls_version = ng_settings.get("angular_language_server_version", DEFAULT_ANGULAR_LANGUAGE_SERVER_VERSION)
svc_version = ng_settings.get("angular_language_service_version", DEFAULT_ANGULAR_LANGUAGE_SERVICE_VERSION) svc_version = ng_settings.get("angular_language_service_version", DEFAULT_ANGULAR_LANGUAGE_SERVICE_VERSION)
ts_version = ng_settings.get("typescript_version", ts_settings.get("typescript_version", DEFAULT_TYPESCRIPT_VERSION)) ts_version = ng_settings.get("typescript_version", ts_settings.get("typescript_version", DEFAULT_TYPESCRIPT_VERSION))
@@ -368,7 +368,7 @@ class AngularLanguageServer(SolidLanguageServer):
def _start_typescript_server(self) -> None: def _start_typescript_server(self) -> None:
try: try:
ts_config = LanguageServerConfig(code_language=Language.TYPESCRIPT, trace_lsp_communication=False) ts_config = LanguageServerConfig(ls_id=LanguageServerId.TYPESCRIPT, trace_lsp_communication=False)
log.info("Creating companion AngularTypeScriptServer") log.info("Creating companion AngularTypeScriptServer")
self._ts_server = AngularTypeScriptServer( self._ts_server = AngularTypeScriptServer(
config=ts_config, config=ts_config,
@@ -413,7 +413,7 @@ class AngularLanguageServer(SolidLanguageServer):
non-fatal: we log and fall back to returning an empty list. non-fatal: we log and fall back to returning an empty list.
""" """
try: try:
html_config = LanguageServerConfig(code_language=Language.HTML, trace_lsp_communication=False) html_config = LanguageServerConfig(ls_id=LanguageServerId.HTML, trace_lsp_communication=False)
log.info("Creating companion VsCodeHtmlLanguageServer") log.info("Creating companion VsCodeHtmlLanguageServer")
self._html_server = VsCodeHtmlLanguageServer( self._html_server = VsCodeHtmlLanguageServer(
config=html_config, config=html_config,
@@ -32,7 +32,7 @@ class BasedPyrightLanguageServer(SolidLanguageServer):
config, config,
repository_root_path, repository_root_path,
None, None,
str(config.code_language), str(config.ls_id),
solidlsp_settings, solidlsp_settings,
) )
@@ -8,7 +8,7 @@ from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from ..ls_config import Language, LanguageServerConfig from ..ls_config import LanguageServerConfig, LanguageServerId
from .common import RuntimeDependency, RuntimeDependencyCollection from .common import RuntimeDependency, RuntimeDependencyCollection
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -73,7 +73,7 @@ class DartLanguageServer(SolidLanguageServer):
@classmethod @classmethod
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str: def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
dart_settings = solidlsp_settings.get_ls_specific_settings(Language.DART) dart_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.DART)
dart_sdk_version = dart_settings.get("dart_sdk_version", DEFAULT_DART_SDK_VERSION) dart_sdk_version = dart_settings.get("dart_sdk_version", DEFAULT_DART_SDK_VERSION)
deps = RuntimeDependencyCollection( deps = RuntimeDependencyCollection(
[ [
@@ -10,7 +10,7 @@ from typing import Any
from overrides import override from overrides import override
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -77,7 +77,7 @@ class ElixirTools(SolidLanguageServer):
Setup runtime dependencies for Expert. Setup runtime dependencies for Expert.
Downloads the Expert binary for the current platform and returns the path to the executable. Downloads the Expert binary for the current platform and returns the path to the executable.
""" """
elixir_settings = solidlsp_settings.get_ls_specific_settings(Language.ELIXIR) elixir_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.ELIXIR)
expert_version = elixir_settings.get("expert_version", EXPERT_VERSION) expert_version = elixir_settings.get("expert_version", EXPERT_VERSION)
# Check if Elixir is available first # Check if Elixir is available first
elixir_version = cls._get_elixir_version() elixir_version = cls._get_elixir_version()
@@ -12,7 +12,7 @@ from sensai.util.logging import LogTime
from solidlsp import ls_types from solidlsp import ls_types
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -73,7 +73,7 @@ class ElmLanguageServer(SolidLanguageServer):
""" """
Setup runtime dependencies for Elm Language Server and return the command to start the server. Setup runtime dependencies for Elm Language Server and return the command to start the server.
""" """
elm_config = solidlsp_settings.get_ls_specific_settings(Language.ELM) elm_config = solidlsp_settings.get_ls_specific_settings(LanguageServerId.ELM)
elm_language_server_version = elm_config.get("elm_language_server_version", DEFAULT_ELM_LANGUAGE_SERVER_VERSION) elm_language_server_version = elm_config.get("elm_language_server_version", DEFAULT_ELM_LANGUAGE_SERVER_VERSION)
elm_compiler_version = elm_config.get("elm_compiler_version", DEFAULT_ELM_COMPILER_VERSION) elm_compiler_version = elm_config.get("elm_compiler_version", DEFAULT_ELM_COMPILER_VERSION)
npm_registry = elm_config.get("npm_registry") npm_registry = elm_config.get("npm_registry")
@@ -13,7 +13,7 @@ from overrides import override
from serena.util.dotnet import DotNETUtil from serena.util.dotnet import DotNETUtil
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -71,7 +71,7 @@ class FSharpLanguageServer(SolidLanguageServer):
""" """
Setup runtime dependencies for F# Language Server and return the command to start the server. Setup runtime dependencies for F# Language Server and return the command to start the server.
""" """
fsharp_settings = solidlsp_settings.get_ls_specific_settings(Language.FSHARP) fsharp_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.FSHARP)
fsautocomplete_version = fsharp_settings.get("fsautocomplete_version", DEFAULT_FSAUTOCOMPLETE_VERSION) fsautocomplete_version = fsharp_settings.get("fsautocomplete_version", DEFAULT_FSAUTOCOMPLETE_VERSION)
dotnet_exe = DotNETUtil("8.0", allow_higher_version=True).get_dotnet_path_or_raise() dotnet_exe = DotNETUtil("8.0", allow_higher_version=True).get_dotnet_path_or_raise()
@@ -81,7 +81,7 @@ class GodotLanguageServer(SolidLanguageServer):
self._conn_info = TCPConnectionInfo(host="127.0.0.1", port=port) self._conn_info = TCPConnectionInfo(host="127.0.0.1", port=port)
return TCPLanguageServer( return TCPLanguageServer(
connection_info=self._conn_info, connection_info=self._conn_info,
language=self.language, ls_id=self.ls_id,
determine_log_level=self._determine_log_level, determine_log_level=self._determine_log_level,
logger=logging_fn, logger=logging_fn,
request_timeout=request_timeout, request_timeout=request_timeout,
@@ -8,7 +8,7 @@ import os
import shlex import shlex
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_utils import FileUtils, PlatformUtils from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -82,7 +82,7 @@ class GroovyLanguageServer(SolidLanguageServer):
ls_jar_options = [] ls_jar_options = []
if solidlsp_settings.ls_specific_settings: if solidlsp_settings.ls_specific_settings:
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY) groovy_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.GROOVY)
jar_options_str = groovy_settings.get("ls_jar_options", "") jar_options_str = groovy_settings.get("ls_jar_options", "")
if jar_options_str: if jar_options_str:
ls_jar_options = shlex.split(jar_options_str) ls_jar_options = shlex.split(jar_options_str)
@@ -111,7 +111,7 @@ class GroovyLanguageServer(SolidLanguageServer):
Setup runtime dependencies for Groovy Language Server and return paths. Setup runtime dependencies for Groovy Language Server and return paths.
""" """
platform_id = PlatformUtils.get_platform_id() platform_id = PlatformUtils.get_platform_id()
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY) groovy_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.GROOVY)
vscode_java_version = groovy_settings.get("vscode_java_version", DEFAULT_VSCODE_JAVA_VERSION) vscode_java_version = groovy_settings.get("vscode_java_version", DEFAULT_VSCODE_JAVA_VERSION)
vscode_java_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}" vscode_java_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}"
@@ -125,7 +125,7 @@ class GroovyLanguageServer(SolidLanguageServer):
java_path = None java_path = None
if solidlsp_settings and solidlsp_settings.ls_specific_settings: if solidlsp_settings and solidlsp_settings.ls_specific_settings:
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY) groovy_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.GROOVY)
custom_java_home = groovy_settings.get("ls_java_home_path") custom_java_home = groovy_settings.get("ls_java_home_path")
if custom_java_home: if custom_java_home:
log.info(f"Using custom Java home path from configuration: {custom_java_home}") log.info(f"Using custom Java home path from configuration: {custom_java_home}")
@@ -226,7 +226,7 @@ class GroovyLanguageServer(SolidLanguageServer):
Find Groovy Language Server JAR file Find Groovy Language Server JAR file
""" """
if solidlsp_settings and solidlsp_settings.ls_specific_settings: if solidlsp_settings and solidlsp_settings.ls_specific_settings:
groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY) groovy_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.GROOVY)
config_jar_path = groovy_settings.get("ls_jar_path") config_jar_path = groovy_settings.get("ls_jar_path")
if config_jar_path and os.path.exists(config_jar_path): if config_jar_path and os.path.exists(config_jar_path):
log.info(f"Using Groovy LS JAR from configuration: {config_jar_path}") log.info(f"Using Groovy LS JAR from configuration: {config_jar_path}")
@@ -115,7 +115,7 @@ class Intelephense(SolidLanguageServer):
# such that Serena's symbol tools treat the added extensions as PHP sources as well (#1710) # such that Serena's symbol tools treat the added extensions as PHP sources as well (#1710)
file_filter = self._custom_settings.get("file_filter") file_filter = self._custom_settings.get("file_filter")
if file_filter: if file_filter:
self.language.get_source_fn_matcher().add_extensions(*file_filter) self.ls_id.get_source_fn_matcher().add_extensions(*file_filter)
def _create_dependency_provider(self) -> LanguageServerDependencyProvider: def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir) return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
@@ -201,7 +201,7 @@ class Intelephense(SolidLanguageServer):
# the initialize request: Intelephense reads configuration exclusively from # the initialize request: Intelephense reads configuration exclusively from
# workspace/didChangeConfiguration (its initializationOptions only cover storagePath, # workspace/didChangeConfiguration (its initializationOptions only cover storagePath,
# globalStoragePath, clearCache and licenceKey). # globalStoragePath, clearCache and licenceKey).
associations = [f"*{ext}" for ext in self.language.get_source_fn_matcher().file_extensions] associations = [f"*{ext}" for ext in self.ls_id.get_source_fn_matcher().file_extensions]
intelephense_config: DidChangeConfigurationParams = {"settings": {"intelephense": {"files": {"associations": associations}}}} intelephense_config: DidChangeConfigurationParams = {"settings": {"intelephense": {"files": {"associations": associations}}}}
log.info(f"Sending workspace/didChangeConfiguration with file associations: {associations}") log.info(f"Sending workspace/didChangeConfiguration with file associations: {associations}")
self.server.notify.workspace_did_change_configuration(intelephense_config) self.server.notify.workspace_did_change_configuration(intelephense_config)
+3 -3
View File
@@ -15,7 +15,7 @@ from pathlib import Path
from overrides import override from overrides import override
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import FileUtils from solidlsp.ls_utils import FileUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -91,7 +91,7 @@ class LuaLanguageServer(SolidLanguageServer):
] ]
if solidlsp_settings is not None: if solidlsp_settings is not None:
lua_settings = solidlsp_settings.get_ls_specific_settings(Language.LUA) lua_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.LUA)
lua_ls_version = lua_settings.get("lua_language_server_version", DEFAULT_LUA_LS_VERSION) lua_ls_version = lua_settings.get("lua_language_server_version", DEFAULT_LUA_LS_VERSION)
ls_resource_dir = _lua_ls_install_dir(LuaLanguageServer.ls_resources_dir(solidlsp_settings), lua_ls_version) ls_resource_dir = _lua_ls_install_dir(LuaLanguageServer.ls_resources_dir(solidlsp_settings), lua_ls_version)
possible_paths.extend( possible_paths.extend(
@@ -118,7 +118,7 @@ class LuaLanguageServer(SolidLanguageServer):
@staticmethod @staticmethod
def _download_lua_ls(solidlsp_settings: SolidLSPSettings) -> str: def _download_lua_ls(solidlsp_settings: SolidLSPSettings) -> str:
"""Download and install lua-language-server if not present.""" """Download and install lua-language-server if not present."""
lua_settings = solidlsp_settings.get_ls_specific_settings(Language.LUA) lua_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.LUA)
lua_ls_version = lua_settings.get("lua_language_server_version", DEFAULT_LUA_LS_VERSION) lua_ls_version = lua_settings.get("lua_language_server_version", DEFAULT_LUA_LS_VERSION)
system = platform.system() system = platform.system()
machine = platform.machine().lower() machine = platform.machine().lower()
+2 -2
View File
@@ -11,7 +11,7 @@ from collections.abc import Iterable
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -152,7 +152,7 @@ class OmniSharp(SolidLanguageServer):
with open(os.path.join(os.path.dirname(__file__), "omnisharp", "runtime_dependencies.json"), encoding="utf-8") as f: with open(os.path.join(os.path.dirname(__file__), "omnisharp", "runtime_dependencies.json"), encoding="utf-8") as f:
d = json.load(f) d = json.load(f)
del d["_description"] del d["_description"]
omnisharp_settings = solidlsp_settings.get_ls_specific_settings(Language.CSHARP_OMNISHARP) omnisharp_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.CSHARP_OMNISHARP)
omnisharp_version = omnisharp_settings.get("omnisharp_version", DEFAULT_OMNISHARP_VERSION) omnisharp_version = omnisharp_settings.get("omnisharp_version", DEFAULT_OMNISHARP_VERSION)
razor_omnisharp_version = omnisharp_settings.get("razor_omnisharp_version", DEFAULT_RAZOR_OMNISHARP_VERSION) razor_omnisharp_version = omnisharp_settings.get("razor_omnisharp_version", DEFAULT_RAZOR_OMNISHARP_VERSION)
for dependency in d["runtimeDependencies"]: for dependency in d["runtimeDependencies"]:
@@ -62,7 +62,7 @@ import zipfile
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, quote_windows_path from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, quote_windows_path
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -131,9 +131,9 @@ class PascalLanguageServer(SolidLanguageServer):
proc_env: dict[str, str] = {} proc_env: dict[str, str] = {}
# Read from ls_specific_settings["pascal"] # Read from ls_specific_settings["pascal"]
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
pascal_settings = solidlsp_settings.get_ls_specific_settings(Language.PASCAL) pascal_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.PASCAL)
# pp: Path to FPC compiler driver (must be fpc.exe, NOT ppc386.exe/ppcx64.exe) # pp: Path to FPC compiler driver (must be fpc.exe, NOT ppc386.exe/ppcx64.exe)
# CodeTools queries fpc.exe for configuration via "fpc -iV", "fpc -iTO", etc. # CodeTools queries fpc.exe for configuration via "fpc -iV", "fpc -iTO", etc.
@@ -638,7 +638,7 @@ class PascalLanguageServer(SolidLanguageServer):
str: The command to start the pasls server str: The command to start the pasls server
""" """
pascal_settings = solidlsp_settings.get_ls_specific_settings(Language.PASCAL) pascal_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.PASCAL)
pasls_version = pascal_settings.get("pasls_version", PASLS_VERSION) pasls_version = pascal_settings.get("pasls_version", PASLS_VERSION)
cls.PASLS_VERSION = pasls_version cls.PASLS_VERSION = pasls_version
cls.PASLS_RELEASES_URL = f"https://github.com/zen010101/pascal-language-server/releases/download/{pasls_version}" cls.PASLS_RELEASES_URL = f"https://github.com/zen010101/pascal-language-server/releases/download/{pasls_version}"
@@ -12,7 +12,7 @@ from typing import Any
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_utils import PlatformId, PlatformUtils from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import DidChangeConfigurationParams from solidlsp.lsp_protocol_handler.lsp_types import DidChangeConfigurationParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -120,7 +120,7 @@ class PerlLanguageServer(SolidLanguageServer):
``ignore_dirs``); falls back to the defaults otherwise. Extracted as a pure function so the ``ignore_dirs``); falls back to the defaults otherwise. Extracted as a pure function so the
configuration plumbing can be unit-tested without starting the language server. configuration plumbing can be unit-tested without starting the language server.
""" """
perl_settings = solidlsp_settings.get_ls_specific_settings(Language.PERL) perl_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.PERL)
file_filter = perl_settings.get("file_filter", list(_DEFAULT_FILE_FILTER)) file_filter = perl_settings.get("file_filter", list(_DEFAULT_FILE_FILTER))
ignore_dirs = perl_settings.get("ignore_dirs", list(_DEFAULT_IGNORE_DIRS)) ignore_dirs = perl_settings.get("ignore_dirs", list(_DEFAULT_IGNORE_DIRS))
return file_filter, ignore_dirs return file_filter, ignore_dirs
@@ -135,7 +135,7 @@ class PerlLanguageServer(SolidLanguageServer):
language composition detection. Without this, ``find_symbol`` would not surface symbols in language composition detection. Without this, ``find_symbol`` would not surface symbols in
files whose extensions were added to ``file_filter`` (#1449). files whose extensions were added to ``file_filter`` (#1449).
""" """
Language.PERL.get_source_fn_matcher().add_extensions(*file_filter) LanguageServerId.PERL.get_source_fn_matcher().add_extensions(*file_filter)
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
# Setup runtime dependencies before initializing # Setup runtime dependencies before initializing
+1 -3
View File
@@ -12,7 +12,7 @@ import subprocess
from overrides import override from overrides import override
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_utils import FileUtils from solidlsp.ls_utils import FileUtils
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -109,8 +109,6 @@ class PhpactorServer(SolidLanguageServer):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
super().__init__(config, repository_root_path, None, "php", solidlsp_settings) super().__init__(config, repository_root_path, None, "php", solidlsp_settings)
# Override internal language enum for correct file matching
self.language = Language.PHP_PHPACTOR
self._ignored_dirnames = {"node_modules", "cache"} self._ignored_dirnames = {"node_modules", "cache"}
if self._custom_settings.get("ignore_vendor", True): if self._custom_settings.get("ignore_vendor", True):
+1 -4
View File
@@ -17,7 +17,7 @@ from solidlsp.ls import (
LSPFileBuffer, LSPFileBuffer,
SolidLanguageServer, SolidLanguageServer,
) )
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_utils import PlatformId, PlatformUtils from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler import lsp_types as protocol_lsp_types from solidlsp.lsp_protocol_handler import lsp_types as protocol_lsp_types
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink
@@ -165,9 +165,6 @@ class PHPantomServer(SolidLanguageServer):
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
super().__init__(config, repository_root_path, None, "php", solidlsp_settings) super().__init__(config, repository_root_path, None, "php", solidlsp_settings)
self.request_id = 0
self.language = Language.PHP_PHPANTOM
self._ignored_dirnames = {"node_modules", "cache"} self._ignored_dirnames = {"node_modules", "cache"}
if self._custom_settings.get("ignore_vendor", True): if self._custom_settings.get("ignore_vendor", True):
self._ignored_dirnames.add("vendor") self._ignored_dirnames.add("vendor")
@@ -23,7 +23,7 @@ from overrides import override
from solidlsp import ls_types from solidlsp import ls_types
from solidlsp.ls import LSPConstants, RawDocumentSymbol, SolidLanguageServer from solidlsp.ls import LSPConstants, RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import FileUtils from solidlsp.ls_utils import FileUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -119,7 +119,7 @@ class PowerShellLanguageServer(SolidLanguageServer):
@classmethod @classmethod
def _get_pses_path(cls, solidlsp_settings: SolidLSPSettings) -> str | None: def _get_pses_path(cls, solidlsp_settings: SolidLSPSettings) -> str | None:
"""Get the path to PowerShell Editor Services installation.""" """Get the path to PowerShell Editor Services installation."""
ps_settings = solidlsp_settings.get_ls_specific_settings(Language.POWERSHELL) ps_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.POWERSHELL)
pses_version = ps_settings.get("pses_version", DEFAULT_PSES_VERSION) pses_version = ps_settings.get("pses_version", DEFAULT_PSES_VERSION)
install_dir = _pses_install_dir(cls.ls_resources_dir(solidlsp_settings), pses_version) install_dir = _pses_install_dir(cls.ls_resources_dir(solidlsp_settings), pses_version)
start_script = install_dir / "PowerShellEditorServices" / "Start-EditorServices.ps1" start_script = install_dir / "PowerShellEditorServices" / "Start-EditorServices.ps1"
@@ -132,7 +132,7 @@ class PowerShellLanguageServer(SolidLanguageServer):
@classmethod @classmethod
def _download_pses(cls, solidlsp_settings: SolidLSPSettings) -> str: def _download_pses(cls, solidlsp_settings: SolidLSPSettings) -> str:
"""Download and install PowerShell Editor Services.""" """Download and install PowerShell Editor Services."""
ps_settings = solidlsp_settings.get_ls_specific_settings(Language.POWERSHELL) ps_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.POWERSHELL)
pses_version = ps_settings.get("pses_version", DEFAULT_PSES_VERSION) pses_version = ps_settings.get("pses_version", DEFAULT_PSES_VERSION)
download_url = ( download_url = (
f"https://github.com/PowerShell/PowerShellEditorServices/releases/download/v{pses_version}/PowerShellEditorServices.zip" f"https://github.com/PowerShell/PowerShellEditorServices/releases/download/v{pses_version}/PowerShellEditorServices.zip"
@@ -184,7 +184,7 @@ class PowerShellLanguageServer(SolidLanguageServer):
# The bundled modules path is the directory containing PowerShellEditorServices # The bundled modules path is the directory containing PowerShellEditorServices
bundled_modules_path = str(Path(pses_path).parent) bundled_modules_path = str(Path(pses_path).parent)
psscriptanalyzer_version = solidlsp_settings.get_ls_specific_settings(Language.POWERSHELL).get( psscriptanalyzer_version = solidlsp_settings.get_ls_specific_settings(LanguageServerId.POWERSHELL).get(
"psscriptanalyzer_version", PSSCRIPTANALYZER_VERSION "psscriptanalyzer_version", PSSCRIPTANALYZER_VERSION
) )
psscriptanalyzer_path = Path(bundled_modules_path) / "PSScriptAnalyzer" / psscriptanalyzer_version psscriptanalyzer_path = Path(bundled_modules_path) / "PSScriptAnalyzer" / psscriptanalyzer_version
@@ -51,7 +51,7 @@ class PyreflyLanguageServer(SolidLanguageServer):
config, config,
repository_root_path, repository_root_path,
None, None,
str(config.code_language), str(config.ls_id),
solidlsp_settings, solidlsp_settings,
) )
self._ensure_workspace_pyrefly_config() self._ensure_workspace_pyrefly_config()
+2 -2
View File
@@ -18,7 +18,7 @@ import threading
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.lsp_protocol_handler.lsp_types import InitializeResult from solidlsp.lsp_protocol_handler.lsp_types import InitializeResult
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -107,7 +107,7 @@ class RubyLsp(SolidLanguageServer):
Setup runtime dependencies for ruby-lsp and return the command list to start the server. Setup runtime dependencies for ruby-lsp and return the command list to start the server.
Installation strategy: Bundler project > global ruby-lsp > gem install ruby-lsp at the pinned version Installation strategy: Bundler project > global ruby-lsp > gem install ruby-lsp at the pinned version
""" """
ls_specific_settings = solidlsp_settings.get_ls_specific_settings(Language.RUBY) ls_specific_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.RUBY)
ruby_lsp_version = ls_specific_settings.get("ruby_lsp_version", RUBY_LSP_VERSION) ruby_lsp_version = ls_specific_settings.get("ruby_lsp_version", RUBY_LSP_VERSION)
# Detect Ruby version manager environment # Detect Ruby version manager environment
# Using the version manager's exec wrapper ensures commands run with the correct Ruby version # Using the version manager's exec wrapper ensures commands run with the correct Ruby version
@@ -52,7 +52,7 @@ def _get_scala_settings(solidlsp_settings: SolidLSPSettings) -> dict[str, object
- on_stale_lock: StaleLockMode - on_stale_lock: StaleLockMode
- log_multi_instance_notice: bool - log_multi_instance_notice: bool
""" """
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
defaults: dict[str, object] = { defaults: dict[str, object] = {
"metals_version": DEFAULT_METALS_VERSION, "metals_version": DEFAULT_METALS_VERSION,
@@ -64,7 +64,7 @@ def _get_scala_settings(solidlsp_settings: SolidLSPSettings) -> dict[str, object
if not solidlsp_settings.ls_specific_settings: if not solidlsp_settings.ls_specific_settings:
return defaults return defaults
scala_settings = solidlsp_settings.get_ls_specific_settings(Language.SCALA) scala_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.SCALA)
# Parse stale lock mode with validation # Parse stale lock mode with validation
on_stale_lock_str = scala_settings.get("on_stale_lock", DEFAULT_ON_STALE_LOCK) on_stale_lock_str = scala_settings.get("on_stale_lock", DEFAULT_ON_STALE_LOCK)
@@ -120,7 +120,7 @@ class ScalaLanguageServer(SolidLanguageServer):
config, config,
repository_root_path, repository_root_path,
ProcessLaunchInfo(cmd=scala_lsp_executable_path, cwd=repository_root_path), ProcessLaunchInfo(cmd=scala_lsp_executable_path, cwd=repository_root_path),
config.code_language.value, config.ls_id.value,
solidlsp_settings, solidlsp_settings,
) )
@@ -40,11 +40,6 @@ class Solargraph(SolidLanguageServer):
"ruby", "ruby",
solidlsp_settings, solidlsp_settings,
) )
# Override internal language enum for file matching (excludes .erb files)
# while keeping LSP languageId as "ruby" for protocol compliance
from solidlsp.ls_config import Language
self.language = Language.RUBY_SOLARGRAPH
self.analysis_complete = threading.Event() self.analysis_complete = threading.Event()
self.service_ready_event = threading.Event() self.service_ready_event = threading.Event()
self.initialize_searcher_command_available = threading.Event() self.initialize_searcher_command_available = threading.Event()
@@ -27,7 +27,7 @@ from solidlsp.ls import (
LSPFileBuffer, LSPFileBuffer,
SolidLanguageServer, SolidLanguageServer,
) )
from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig from solidlsp.ls_config import FilenameMatcher, LanguageServerConfig, LanguageServerId
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -100,14 +100,14 @@ class SvelteTypeScriptServer(TypeScriptLanguageServer):
@classmethod @classmethod
@override @override
def get_language_enum_instance(cls) -> Language: def get_language_server_id(cls) -> LanguageServerId:
"""Return TYPESCRIPT; companion uses the TypeScript LS infrastructure.""" """Return TYPESCRIPT; companion uses the TypeScript LS infrastructure."""
return Language.TYPESCRIPT return LanguageServerId.TYPESCRIPT
@override @override
def get_source_fn_matcher(self) -> FilenameMatcher: def get_source_fn_matcher(self) -> FilenameMatcher:
# include .svelte so references returned by the plugin are not filtered out # include .svelte so references returned by the plugin are not filtered out
return Language.SVELTE.get_source_fn_matcher() return LanguageServerId.SVELTE.get_source_fn_matcher()
@override @override
def _create_dependency_provider(self) -> LanguageServerDependencyProvider: def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
@@ -273,7 +273,7 @@ class SvelteLanguageServer(SolidLanguageServer):
@override @override
def _create_dependency_provider(self) -> LanguageServerDependencyProvider: def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
ts_settings = self._solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT) ts_settings = self._solidlsp_settings.get_ls_specific_settings(LanguageServerId.TYPESCRIPT)
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir, ts_settings) return self.DependencyProvider(self._custom_settings, self._ls_resources_dir, ts_settings)
def __init__(self, config: LanguageServerConfig, repo_path: str, solidlsp_settings: SolidLSPSettings): def __init__(self, config: LanguageServerConfig, repo_path: str, solidlsp_settings: SolidLSPSettings):
@@ -337,7 +337,7 @@ class SvelteLanguageServer(SolidLanguageServer):
def _get_companion_indexing_timeout(self) -> float: def _get_companion_indexing_timeout(self) -> float:
""":return: maximum seconds to wait for companion TS indexing after opening Svelte files.""" """:return: maximum seconds to wait for companion TS indexing after opening Svelte files."""
ts_settings = self._solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT) ts_settings = self._solidlsp_settings.get_ls_specific_settings(LanguageServerId.TYPESCRIPT)
timeout = self._custom_settings.get( timeout = self._custom_settings.get(
"indexing_timeout", "indexing_timeout",
ts_settings.get("indexing_timeout", SvelteTypeScriptServer.INDEXING_PROGRESS_TIMEOUT), ts_settings.get("indexing_timeout", SvelteTypeScriptServer.INDEXING_PROGRESS_TIMEOUT),
@@ -417,7 +417,7 @@ class SvelteLanguageServer(SolidLanguageServer):
"""Spawn the companion :class:`SvelteTypeScriptServer`, wait for ready, then index .svelte files.""" """Spawn the companion :class:`SvelteTypeScriptServer`, wait for ready, then index .svelte files."""
try: try:
ts_config = LanguageServerConfig( ts_config = LanguageServerConfig(
code_language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
trace_lsp_communication=False, trace_lsp_communication=False,
) )
log.info("Creating companion SvelteTypeScriptServer") log.info("Creating companion SvelteTypeScriptServer")
@@ -5,7 +5,7 @@ import shutil
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_utils import PlatformUtils from solidlsp.ls_utils import PlatformUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -124,7 +124,7 @@ class TerraformLS(SolidLanguageServer):
Downloads and installs terraform-ls if not already present. Downloads and installs terraform-ls if not already present.
""" """
cls._ensure_tf_command_available() cls._ensure_tf_command_available()
terraform_settings = solidlsp_settings.get_ls_specific_settings(Language.TERRAFORM) terraform_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.TERRAFORM)
terraform_ls_version = terraform_settings.get("terraform_ls_version", DEFAULT_TERRAFORM_LS_VERSION) terraform_ls_version = terraform_settings.get("terraform_ls_version", DEFAULT_TERRAFORM_LS_VERSION)
platform_id = PlatformUtils.get_platform_id() platform_id = PlatformUtils.get_platform_id()
deps = RuntimeDependencyCollection( deps = RuntimeDependencyCollection(
+1 -1
View File
@@ -34,7 +34,7 @@ class TyLanguageServer(SolidLanguageServer):
config, config,
repository_root_path, repository_root_path,
None, None,
str(config.code_language), str(config.ls_id),
solidlsp_settings, solidlsp_settings,
) )
@@ -12,7 +12,7 @@ import threading
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_utils import PlatformId, PlatformUtils from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
@@ -87,7 +87,7 @@ class VtsLanguageServer(SolidLanguageServer):
PlatformId.WIN_arm64, PlatformId.WIN_arm64,
] ]
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for vtsls at the moment" assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for vtsls at the moment"
vts_config = solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT_VTS) vts_config = solidlsp_settings.get_ls_specific_settings(LanguageServerId.TYPESCRIPT_VTS)
vtsls_version = vts_config.get("vtsls_version", DEFAULT_VTSLS_VERSION) vtsls_version = vts_config.get("vtsls_version", DEFAULT_VTSLS_VERSION)
npm_registry = vts_config.get("npm_registry") npm_registry = vts_config.get("npm_registry")
@@ -22,7 +22,7 @@ from solidlsp.language_servers.typescript_language_server import (
prefer_non_node_modules_definition, prefer_non_node_modules_definition,
) )
from solidlsp.ls import LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer from solidlsp.ls import LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig from solidlsp.ls_config import FilenameMatcher, LanguageServerConfig, LanguageServerId
from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_types import Location from solidlsp.ls_types import Location
from solidlsp.ls_utils import PathUtils from solidlsp.ls_utils import PathUtils
@@ -39,19 +39,19 @@ class VueTypeScriptServer(TypeScriptLanguageServer):
@classmethod @classmethod
@override @override
def get_language_enum_instance(cls) -> Language: def get_language_server_id(cls) -> LanguageServerId:
"""Return TYPESCRIPT since this is a TypeScript language server variant. """Return TYPESCRIPT since this is a TypeScript language server variant.
Note: VueTypeScriptServer is a companion server that uses TypeScript's language server Note: VueTypeScriptServer is a companion server that uses TypeScript's language server
with the Vue TypeScript plugin. It reports as TYPESCRIPT to maintain compatibility with the Vue TypeScript plugin. It reports as TYPESCRIPT to maintain compatibility
with the TypeScript language server infrastructure. with the TypeScript language server infrastructure.
""" """
return Language.TYPESCRIPT return LanguageServerId.TYPESCRIPT
def get_source_fn_matcher(self) -> FilenameMatcher: def get_source_fn_matcher(self) -> FilenameMatcher:
# must override with Vue-specific matcher to ensure .vue files are included (as they can be discovered via references, # must override with Vue-specific matcher to ensure .vue files are included (as they can be discovered via references,
# for instance; otherwise, we may find references in .vue files but then filter the results out, because .vue files are ignored.) # for instance; otherwise, we may find references in .vue files but then filter the results out, because .vue files are ignored.)
return Language.VUE.get_source_fn_matcher() return LanguageServerId.VUE.get_source_fn_matcher()
class DependencyProvider(TypeScriptLanguageServer.DependencyProvider): class DependencyProvider(TypeScriptLanguageServer.DependencyProvider):
"""Dependency provider that returns a pre-resolved executable path. """Dependency provider that returns a pre-resolved executable path.
@@ -536,10 +536,10 @@ class VueLanguageServer(SolidLanguageServer):
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again." assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
# Get TypeScript version settings from TypeScript language server settings # Get TypeScript version settings from TypeScript language server settings
typescript_config = solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT) typescript_config = solidlsp_settings.get_ls_specific_settings(LanguageServerId.TYPESCRIPT)
typescript_version = typescript_config.get("typescript_version", "5.9.3") typescript_version = typescript_config.get("typescript_version", "5.9.3")
typescript_language_server_version = typescript_config.get("typescript_language_server_version", "5.1.3") typescript_language_server_version = typescript_config.get("typescript_language_server_version", "5.1.3")
vue_config = solidlsp_settings.get_ls_specific_settings(Language.VUE) vue_config = solidlsp_settings.get_ls_specific_settings(LanguageServerId.VUE)
vue_language_server_version = vue_config.get("vue_language_server_version", "3.1.5") vue_language_server_version = vue_config.get("vue_language_server_version", "3.1.5")
npm_registry = vue_config.get("npm_registry", typescript_config.get("npm_registry")) npm_registry = vue_config.get("npm_registry", typescript_config.get("npm_registry"))
@@ -659,7 +659,7 @@ class VueLanguageServer(SolidLanguageServer):
vue_ts_plugin_path = os.path.join(self._vue_ls_dir, "node_modules", "@vue", "typescript-plugin") vue_ts_plugin_path = os.path.join(self._vue_ls_dir, "node_modules", "@vue", "typescript-plugin")
ts_config = LanguageServerConfig( ts_config = LanguageServerConfig(
code_language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
trace_lsp_communication=False, trace_lsp_communication=False,
) )
+13 -13
View File
@@ -31,7 +31,7 @@ from solidlsp.dependency_provider import (
LanguageServerDependencyProviderUvx, LanguageServerDependencyProviderUvx,
) )
from solidlsp.initialize_params import DefaultInitializeParamsBuilder, InitializeParamsBuilder from solidlsp.initialize_params import DefaultInitializeParamsBuilder, InitializeParamsBuilder
from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig from solidlsp.ls_config import FilenameMatcher, LanguageServerConfig, LanguageServerId
from solidlsp.ls_exceptions import InvalidTextLocationError, SolidLSPException from solidlsp.ls_exceptions import InvalidTextLocationError, SolidLSPException
from solidlsp.ls_process import LanguageServerInterface, StdioLanguageServer from solidlsp.ls_process import LanguageServerInterface, StdioLanguageServer
from solidlsp.ls_types import UnifiedSymbolInformation from solidlsp.ls_types import UnifiedSymbolInformation
@@ -383,8 +383,8 @@ class SolidLanguageServer(ABC):
return logging.INFO return logging.INFO
@classmethod @classmethod
def get_language_enum_instance(cls) -> Language: def get_language_server_id(cls) -> LanguageServerId:
return Language.from_ls_class(cls) return LanguageServerId.from_ls_class(cls)
@classmethod @classmethod
def supports_implementation_request(cls) -> bool: def supports_implementation_request(cls) -> bool:
@@ -442,7 +442,7 @@ class SolidLanguageServer(ABC):
# Ensure repository_root_path is absolute to avoid issues with file URIs # Ensure repository_root_path is absolute to avoid issues with file URIs
repository_root_path = os.path.abspath(repository_root_path) repository_root_path = os.path.abspath(repository_root_path)
ls_class = config.code_language.get_ls_class() ls_class = config.ls_id.get_ls_class()
# All language server implementations are required to use the same signature of the constructor # All language server implementations are required to use the same signature of the constructor
# (which differs from the signature of the base class constructor). # (which differs from the signature of the base class constructor).
ls = ls_class(config, repository_root_path, solidlsp_settings) ls = ls_class(config, repository_root_path, solidlsp_settings)
@@ -481,13 +481,13 @@ class SolidLanguageServer(ABC):
""" """
self.config = config self.config = config
self._solidlsp_settings = solidlsp_settings self._solidlsp_settings = solidlsp_settings
lang = self.get_language_enum_instance() ls_id = self.get_language_server_id()
self._custom_settings = solidlsp_settings.get_ls_specific_settings(lang) self._custom_settings = solidlsp_settings.get_ls_specific_settings(ls_id)
""" """
the (user-provided) language server-specific settings the (user-provided) language server-specific settings
""" """
self._ls_resources_dir = self.ls_resources_dir(solidlsp_settings) self._ls_resources_dir = self.ls_resources_dir(solidlsp_settings)
log.debug(f"Custom config (LS-specific settings) for {lang}: {self._custom_settings}") log.debug(f"Custom config (LS-specific settings) for {ls_id}: {self._custom_settings}")
self._encoding = config.encoding self._encoding = config.encoding
self.repository_root_path: str = repository_root_path self.repository_root_path: str = repository_root_path
@@ -500,15 +500,15 @@ class SolidLanguageServer(ABC):
default language identifier to be passed to the language server in `textDocument/didOpen` notifications. default language identifier to be passed to the language server in `textDocument/didOpen` notifications.
""" """
self.open_file_buffers: dict[str, LSPFileBuffer] = {} self.open_file_buffers: dict[str, LSPFileBuffer] = {}
self.language = self.get_language_enum_instance() self.ls_id = self.get_language_server_id()
""" """
identifies the language server (not to be confused with the language id passed to the language server) identifies the language server (not to be confused with the language_id passed to the language server)
""" """
# The source filename matcher is a @cache'd per-language singleton. A previous project may # The source filename matcher is a @cache'd per-language singleton. A previous project may
# have extended it (e.g. Perl's file_filter adding .cgi); reset it here so every activation # have extended it (e.g. Perl's file_filter adding .cgi); reset it here so every activation
# starts from the language's default extensions, then language-server subclasses re-apply # starts from the language's default extensions, then language-server subclasses re-apply
# their own settings during the rest of __init__. # their own settings during the rest of __init__.
self.language.get_source_fn_matcher().reset() self.ls_id.get_source_fn_matcher().reset()
self._published_diagnostics: dict[str, list[ls_types.Diagnostic]] = {} self._published_diagnostics: dict[str, list[ls_types.Diagnostic]] = {}
self._published_diagnostics_generation_by_uri: dict[str, int] = {} self._published_diagnostics_generation_by_uri: dict[str, int] = {}
self._published_diagnostics_generation = 0 self._published_diagnostics_generation = 0
@@ -611,7 +611,7 @@ class SolidLanguageServer(ABC):
log.debug(f"Creating language server instance with {language_id=} and {process_launch_info}") log.debug(f"Creating language server instance with {language_id=} and {process_launch_info}")
return StdioLanguageServer( return StdioLanguageServer(
process_launch_info, process_launch_info,
language=self.language, ls_id=self.ls_id,
determine_log_level=self._determine_log_level, determine_log_level=self._determine_log_level,
logger=logging_fn, logger=logging_fn,
start_independent_lsp_process=self.config.start_independent_lsp_process, start_independent_lsp_process=self.config.start_independent_lsp_process,
@@ -1167,7 +1167,7 @@ class SolidLanguageServer(ABC):
are understood by this language server or are discovered as containing sources indirectly, e.g. via references are understood by this language server or are discovered as containing sources indirectly, e.g. via references
""" """
# By default, use the matcher of the language # By default, use the matcher of the language
return self.language.get_source_fn_matcher() return self.ls_id.get_source_fn_matcher()
def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool:
""" """
@@ -3131,7 +3131,7 @@ class SolidLanguageServer(ABC):
:return: self for method chaining :return: self for method chaining
""" """
log.info(f"Starting language server with language {self.language_server.language} for {self.language_server.repository_root_path}") log.info(f"Starting language server {self.language_server.ls_id} for {self.language_server.repository_root_path}")
self.server_started = True self.server_started = True
self._start_server() self._start_server()
return self return self
+2 -2
View File
@@ -87,7 +87,7 @@ class FilenameMatcher:
return False return False
class Language(str, Enum): class LanguageServerId(str, Enum):
""" """
Enumeration of language servers supported by SolidLSP. Enumeration of language servers supported by SolidLSP.
""" """
@@ -895,7 +895,7 @@ class LanguageServerConfig:
Configuration parameters for a language server instance Configuration parameters for a language server instance
""" """
code_language: Language ls_id: LanguageServerId
""" """
defines the language server to use defines the language server to use
""" """
+3 -3
View File
@@ -2,7 +2,7 @@
This module contains the exceptions raised by the framework. This module contains the exceptions raised by the framework.
""" """
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
class SolidLSPException(Exception): class SolidLSPException(Exception):
@@ -28,14 +28,14 @@ class SolidLSPException(Exception):
return isinstance(self.cause, LanguageServerTerminatedException) return isinstance(self.cause, LanguageServerTerminatedException)
def get_affected_language(self) -> Language | None: def get_affected_language(self) -> LanguageServerId | None:
""" """
:return: the affected language for the case where the exception is caused by the language server having terminated :return: the affected language for the case where the exception is caused by the language server having terminated
""" """
from .ls_process import LanguageServerTerminatedException from .ls_process import LanguageServerTerminatedException
if isinstance(self.cause, LanguageServerTerminatedException): if isinstance(self.cause, LanguageServerTerminatedException):
return self.cause.language return self.cause.ls_id
return None return None
def __str__(self) -> str: def __str__(self) -> str:
+24 -24
View File
@@ -14,7 +14,7 @@ from typing import IO, Any, AnyStr, cast
from sensai.util.string import ToStringMixin from sensai.util.string import ToStringMixin
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_request import LanguageServerRequest from solidlsp.ls_request import LanguageServerRequest
from solidlsp.lsp_protocol_handler.lsp_requests import LspNotification from solidlsp.lsp_protocol_handler.lsp_requests import LspNotification
@@ -53,10 +53,10 @@ class LanguageServerTerminatedException(Exception):
Exception raised when the language server process has terminated unexpectedly. Exception raised when the language server process has terminated unexpectedly.
""" """
def __init__(self, message: str, language: Language, cause: Exception | None = None) -> None: def __init__(self, message: str, ls_id: LanguageServerId, cause: Exception | None = None) -> None:
super().__init__(message) super().__init__(message)
self.message = message self.message = message
self.language = language self.ls_id = ls_id
self.cause = cause self.cause = cause
def __str__(self) -> str: def __str__(self) -> str:
@@ -116,18 +116,18 @@ class LanguageServerInterface(ABC):
def __init__( def __init__(
self, self,
language: Language, ls_id: LanguageServerId,
determine_log_level: Callable[[str], int], determine_log_level: Callable[[str], int],
logger: Callable[[str, str, StringDict | str], None] | None = None, logger: Callable[[str, str, StringDict | str], None] | None = None,
request_timeout: float | None = None, request_timeout: float | None = None,
) -> None: ) -> None:
""" """
:param language: the language :param ls_id: the language server identifier
:param determine_log_level: a function for log lines read from stderr, which determines the log level :param determine_log_level: a function for log lines read from stderr, which determines the log level
:param logger: the trace logger function :param logger: the trace logger function
:param request_timeout: the timeout, in seconds, for all requests sent to the language server. If None, no timeout will be applied. :param request_timeout: the timeout, in seconds, for all requests sent to the language server. If None, no timeout will be applied.
""" """
self.language = language self.ls_id = ls_id
self._determine_log_level = determine_log_level self._determine_log_level = determine_log_level
self.send = LanguageServerRequest(self) self.send = LanguageServerRequest(self)
""" """
@@ -486,7 +486,7 @@ class StdioLanguageServer(LanguageServerInterface):
def __init__( def __init__(
self, self,
process_launch_info: ProcessLaunchInfo, process_launch_info: ProcessLaunchInfo,
language: Language, ls_id: LanguageServerId,
determine_log_level: Callable[[str], int], determine_log_level: Callable[[str], int],
logger: Callable[[str, str, StringDict | str], None] | None = None, logger: Callable[[str, str, StringDict | str], None] | None = None,
start_independent_lsp_process: bool = True, start_independent_lsp_process: bool = True,
@@ -494,13 +494,13 @@ class StdioLanguageServer(LanguageServerInterface):
) -> None: ) -> None:
""" """
:param process_launch_info: the information required to launch the language server process :param process_launch_info: the information required to launch the language server process
:param language: the language :param ls_id: the language
:param determine_log_level: a function for log lines read from stderr, which determines the log level :param determine_log_level: a function for log lines read from stderr, which determines the log level
:param logger: the trace logger function :param logger: the trace logger function
:param start_independent_lsp_process: whether to start the language server process in an independent process group :param start_independent_lsp_process: whether to start the language server process in an independent process group
:param request_timeout: the timeout, in seconds, for all requests sent to the language server. If None, no timeout will be applied. :param request_timeout: the timeout, in seconds, for all requests sent to the language server. If None, no timeout will be applied.
""" """
super().__init__(language, determine_log_level, logger, request_timeout) super().__init__(ls_id, determine_log_level, logger, request_timeout)
self.process_launch_info = process_launch_info self.process_launch_info = process_launch_info
self.process: subprocess.Popen[bytes] | None = None self.process: subprocess.Popen[bytes] | None = None
@@ -547,12 +547,12 @@ class StdioLanguageServer(LanguageServerInterface):
# start threads to read stdout and stderr of the process # start threads to read stdout and stderr of the process
threading.Thread( threading.Thread(
target=self._read_ls_process_stdout, target=self._read_ls_process_stdout,
name=f"LSP-stdout-reader:{self.language.value}", name=f"LSP-stdout-reader:{self.ls_id.value}",
daemon=True, daemon=True,
).start() ).start()
threading.Thread( threading.Thread(
target=self._read_ls_process_stderr, target=self._read_ls_process_stderr,
name=f"LSP-stderr-reader:{self.language.value}", name=f"LSP-stderr-reader:{self.ls_id.value}",
daemon=True, daemon=True,
).start() ).start()
@@ -571,7 +571,7 @@ class StdioLanguageServer(LanguageServerInterface):
# Ignore errors here, we are proceeding to terminate anyway. # Ignore errors here, we are proceeding to terminate anyway.
# terminate the process # terminate the process
subprocess_util.terminate_process_tree_with_kill_fallback( subprocess_util.terminate_process_tree_with_kill_fallback(
self.process, terminate_timeout=timeout, process_name=f"LS[{self.language.value}]" self.process, terminate_timeout=timeout, process_name=f"LS[{self.ls_id.value}]"
) )
finally: finally:
self.process = None self.process = None
@@ -594,7 +594,7 @@ class StdioLanguageServer(LanguageServerInterface):
if process.poll() is not None: if process.poll() is not None:
raise LanguageServerTerminatedException( raise LanguageServerTerminatedException(
f"Process terminated while trying to read response (read {len(data)} of {num_bytes} bytes before termination)", f"Process terminated while trying to read response (read {len(data)} of {num_bytes} bytes before termination)",
language=self.language, ls_id=self.ls_id,
) )
# Process still running but no data available yet, retry after a short delay # Process still running but no data available yet, retry after a short delay
time.sleep(0.01) time.sleep(0.01)
@@ -631,15 +631,15 @@ class StdioLanguageServer(LanguageServerInterface):
except LanguageServerTerminatedException as e: except LanguageServerTerminatedException as e:
exception = e exception = e
except (BrokenPipeError, ConnectionResetError) as e: except (BrokenPipeError, ConnectionResetError) as e:
exception = LanguageServerTerminatedException("Language server process terminated while reading stdout", self.language, cause=e) exception = LanguageServerTerminatedException("Language server process terminated while reading stdout", self.ls_id, cause=e)
except Exception as e: except Exception as e:
exception = LanguageServerTerminatedException( exception = LanguageServerTerminatedException(
"Unexpected error while reading stdout from language server process", self.language, cause=e "Unexpected error while reading stdout from language server process", self.ls_id, cause=e
) )
log.info("Language server stdout reader thread has terminated") log.info("Language server stdout reader thread has terminated")
if not self._is_stopping: if not self._is_stopping:
if exception is None: if exception is None:
exception = LanguageServerTerminatedException("Language server stdout read process terminated unexpectedly", self.language) exception = LanguageServerTerminatedException("Language server stdout read process terminated unexpectedly", self.ls_id)
log.error(str(exception)) log.error(str(exception))
self._cancel_pending_requests(exception) self._cancel_pending_requests(exception)
@@ -704,12 +704,12 @@ class TCPLanguageServer(LanguageServerInterface):
def __init__( def __init__(
self, self,
connection_info: TCPConnectionInfo, connection_info: TCPConnectionInfo,
language: Language, ls_id: LanguageServerId,
determine_log_level: Callable[[str], int], determine_log_level: Callable[[str], int],
logger: Callable[[str, str, StringDict | str], None] | None = None, logger: Callable[[str, str, StringDict | str], None] | None = None,
request_timeout: float | None = None, request_timeout: float | None = None,
) -> None: ) -> None:
super().__init__(language, determine_log_level, logger, request_timeout) super().__init__(ls_id, determine_log_level, logger, request_timeout)
self._connection_info = connection_info self._connection_info = connection_info
self._sock: socket.socket | None = None self._sock: socket.socket | None = None
self._file: Any = None # socket.makefile("rb") - buffered reader self._file: Any = None # socket.makefile("rb") - buffered reader
@@ -747,7 +747,7 @@ class TCPLanguageServer(LanguageServerInterface):
threading.Thread( threading.Thread(
target=self._read_loop, target=self._read_loop,
name=f"LSP-tcp-reader:{self.language.value}", name=f"LSP-tcp-reader:{self.ls_id.value}",
daemon=True, daemon=True,
).start() ).start()
@@ -788,7 +788,7 @@ class TCPLanguageServer(LanguageServerInterface):
log.error("Failed to write to TCP language server: %s", e) log.error("Failed to write to TCP language server: %s", e)
self._sock = None self._sock = None
self._file = None self._file = None
self._cancel_pending_requests(LanguageServerTerminatedException("TCP send error", self.language, cause=e)) self._cancel_pending_requests(LanguageServerTerminatedException("TCP send error", self.ls_id, cause=e))
def _read_loop(self) -> None: def _read_loop(self) -> None:
"""Read Content-Length-framed LSP messages from the TCP socket and dispatch them.""" """Read Content-Length-framed LSP messages from the TCP socket and dispatch them."""
@@ -802,7 +802,7 @@ class TCPLanguageServer(LanguageServerInterface):
line = f.readline() line = f.readline()
except OSError as exc: except OSError as exc:
if not self._is_stopping: if not self._is_stopping:
exception = LanguageServerTerminatedException("TCP read error", self.language, cause=exc) exception = LanguageServerTerminatedException("TCP read error", self.ls_id, cause=exc)
break break
if not line: if not line:
break break
@@ -824,17 +824,17 @@ class TCPLanguageServer(LanguageServerInterface):
body = f.read(num_bytes) body = f.read(num_bytes)
except OSError as exc: except OSError as exc:
if not self._is_stopping: if not self._is_stopping:
exception = LanguageServerTerminatedException("TCP read error", self.language, cause=exc) exception = LanguageServerTerminatedException("TCP read error", self.ls_id, cause=exc)
break break
if len(body) < num_bytes: if len(body) < num_bytes:
break break
self._handle_body(body) self._handle_body(body)
except Exception as exc: except Exception as exc:
exception = LanguageServerTerminatedException("Unexpected error in TCP language server read loop", self.language, cause=exc) exception = LanguageServerTerminatedException("Unexpected error in TCP language server read loop", self.ls_id, cause=exc)
log.info("TCP language server read loop has terminated") log.info("TCP language server read loop has terminated")
if not self._is_stopping: if not self._is_stopping:
if exception is None: if exception is None:
exception = LanguageServerTerminatedException("TCP language server read loop terminated unexpectedly", self.language) exception = LanguageServerTerminatedException("TCP language server read loop terminated unexpectedly", self.ls_id)
log.error(str(exception)) log.error(str(exception))
self._cancel_pending_requests(exception) self._cancel_pending_requests(exception)
# Clear the socket so is_running() returns False, allowing _ensure_functional_ls # Clear the socket so is_running() returns False, allowing _ensure_functional_ls
+11 -7
View File
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any
from sensai.util.string import ToStringMixin from sensai.util.string import ToStringMixin
if TYPE_CHECKING: if TYPE_CHECKING:
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ class SolidLSPSettings:
For instance, if this is "/home/user/myproject/.solidlsp", For instance, if this is "/home/user/myproject/.solidlsp",
then Solid-LSP will store project-specific data (e.g. caches) in that directory. then Solid-LSP will store project-specific data (e.g. caches) in that directory.
""" """
ls_specific_settings: dict["Language", dict[str, Any]] = field(default_factory=dict) ls_specific_settings: dict["LanguageServerId", dict[str, Any]] = field(default_factory=dict)
""" """
Advanced configuration option allowing to configure language server implementation specific options. Advanced configuration option allowing to configure language server implementation specific options.
Have a look at the docstring of the constructors of the corresponding LS implementations within solidlsp to see which options are available. Have a look at the docstring of the constructors of the corresponding LS implementations within solidlsp to see which options are available.
@@ -50,6 +50,10 @@ class SolidLSPSettings:
return os.path.join(str(self.solidlsp_dir), "language_servers", "static") return os.path.join(str(self.solidlsp_dir), "language_servers", "static")
class CustomLSSettings(ToStringMixin): class CustomLSSettings(ToStringMixin):
"""
Represents custom (user-specified) settings for a specific language server.
"""
def __init__(self, settings: dict[str, Any] | None) -> None: def __init__(self, settings: dict[str, Any] | None) -> None:
self.settings = settings or {} self.settings = settings or {}
@@ -69,11 +73,11 @@ class SolidLSPSettings:
value = default_value value = default_value
return value return value
def get_ls_specific_settings(self, language: "Language") -> CustomLSSettings: def get_ls_specific_settings(self, ls_id: "LanguageServerId") -> CustomLSSettings:
""" """
Get the language server specific settings for the given language. Gets the custom settings for the given language server
:param language: The programming language. :param ls_id: the language server identifier for which to retrieve settings
:return: A dictionary of settings for the language server. :return: a dictionary of settings for the language server
""" """
return self.CustomLSSettings(self.ls_specific_settings.get(language)) return self.CustomLSSettings(self.ls_specific_settings.get(ls_id))
+113 -113
View File
@@ -19,7 +19,7 @@ from serena.constants import SERENA_MANAGED_DIR_NAME
from serena.project import Project from serena.project import Project
from serena.util.file_system import GitignoreParser from serena.util.file_system import GitignoreParser
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from .solidlsp.clojure import is_clojure_cli_available from .solidlsp.clojure import is_clojure_cli_available
@@ -39,47 +39,47 @@ def resources_dir() -> Path:
class LanguageParamRequest: class LanguageParamRequest:
param: Language param: LanguageServerId
_LANGUAGE_REPO_ALIASES: dict[Language, Language] = { _LANGUAGE_REPO_ALIASES: dict[LanguageServerId, LanguageServerId] = {
Language.CPP_CCLS: Language.CPP, LanguageServerId.CPP_CCLS: LanguageServerId.CPP,
Language.PHP_PHPACTOR: Language.PHP, LanguageServerId.PHP_PHPACTOR: LanguageServerId.PHP,
Language.PHP_PHPANTOM: Language.PHP, LanguageServerId.PHP_PHPANTOM: LanguageServerId.PHP,
Language.PYTHON_JEDI: Language.PYTHON, LanguageServerId.PYTHON_JEDI: LanguageServerId.PYTHON,
Language.PYTHON_BASEDPYRIGHT: Language.PYTHON, LanguageServerId.PYTHON_BASEDPYRIGHT: LanguageServerId.PYTHON,
Language.PYTHON_TY: Language.PYTHON, LanguageServerId.PYTHON_TY: LanguageServerId.PYTHON,
Language.RUBY_SOLARGRAPH: Language.RUBY, LanguageServerId.RUBY_SOLARGRAPH: LanguageServerId.RUBY,
Language.PYTHON_TY: Language.PYTHON, LanguageServerId.PYTHON_TY: LanguageServerId.PYTHON,
Language.PYTHON_PYREFLY: Language.PYTHON, LanguageServerId.PYTHON_PYREFLY: LanguageServerId.PYTHON,
} }
PYTHON_LANGUAGE_BACKENDS = [Language.PYTHON, Language.PYTHON_TY, Language.PYTHON_BASEDPYRIGHT] PYTHON_LANGUAGE_BACKENDS = [LanguageServerId.PYTHON, LanguageServerId.PYTHON_TY, LanguageServerId.PYTHON_BASEDPYRIGHT]
def get_repo_path(language: Language) -> Path: def get_repo_path(language: LanguageServerId) -> Path:
repo_language = _LANGUAGE_REPO_ALIASES.get(language, language) repo_language = _LANGUAGE_REPO_ALIASES.get(language, language)
return Path(__file__).parent / "resources" / "repos" / repo_language / "test_repo" return Path(__file__).parent / "resources" / "repos" / repo_language / "test_repo"
def _create_ls( def _create_ls(
language: Language, ls_id: LanguageServerId,
repo_path: str | None = None, repo_path: str | None = None,
ignored_paths: list[str] | None = None, ignored_paths: list[str] | None = None,
trace_lsp_communication: bool = False, trace_lsp_communication: bool = False,
ls_specific_settings: dict[Language, dict[str, Any]] | None = None, ls_specific_settings: dict[LanguageServerId, dict[str, Any]] | None = None,
workspace_folders: list[str] | None = None, workspace_folders: list[str] | None = None,
additional_workspace_folders: list[str] | None = None, additional_workspace_folders: list[str] | None = None,
solidlsp_dir: Path | None = None, solidlsp_dir: Path | None = None,
) -> SolidLanguageServer: ) -> SolidLanguageServer:
ignored_paths = ignored_paths or [] ignored_paths = ignored_paths or []
if repo_path is None: if repo_path is None:
repo_path = str(get_repo_path(language)) repo_path = str(get_repo_path(ls_id))
gitignore_parser = GitignoreParser(str(repo_path)) gitignore_parser = GitignoreParser(str(repo_path))
for spec in gitignore_parser.get_ignore_specs(): for spec in gitignore_parser.get_ignore_specs():
ignored_paths.extend(spec.patterns) ignored_paths.extend(spec.patterns)
config = LanguageServerConfig( config = LanguageServerConfig(
code_language=language, ls_id=ls_id,
ignored_paths=ignored_paths, ignored_paths=ignored_paths,
trace_lsp_communication=trace_lsp_communication, trace_lsp_communication=trace_lsp_communication,
workspace_folders=workspace_folders or ["."], workspace_folders=workspace_folders or ["."],
@@ -100,17 +100,17 @@ def _create_ls(
@contextmanager @contextmanager
def start_ls_context( def start_ls_context(
language: Language, ls_id: LanguageServerId,
repo_path: str | None = None, repo_path: str | None = None,
ignored_paths: list[str] | None = None, ignored_paths: list[str] | None = None,
trace_lsp_communication: bool = False, trace_lsp_communication: bool = False,
ls_specific_settings: dict[Language, dict[str, Any]] | None = None, ls_specific_settings: dict[LanguageServerId, dict[str, Any]] | None = None,
workspace_folders: list[str] | None = None, workspace_folders: list[str] | None = None,
additional_workspace_folders: list[str] | None = None, additional_workspace_folders: list[str] | None = None,
solidlsp_dir: Path | None = None, solidlsp_dir: Path | None = None,
) -> Iterator[SolidLanguageServer]: ) -> Iterator[SolidLanguageServer]:
ls = _create_ls( ls = _create_ls(
language, ls_id,
repo_path, repo_path,
ignored_paths, ignored_paths,
trace_lsp_communication, trace_lsp_communication,
@@ -119,14 +119,14 @@ def start_ls_context(
additional_workspace_folders, additional_workspace_folders,
solidlsp_dir, solidlsp_dir,
) )
log.info(f"Starting language server for {language} {repo_path}") log.info(f"Starting language server for {ls_id} {repo_path}")
with ls.start_server_context(): with ls.start_server_context():
yield ls yield ls
@contextmanager @contextmanager
def start_default_ls_context(language: Language) -> Iterator[SolidLanguageServer]: def start_default_ls_context(ls_id: LanguageServerId) -> Iterator[SolidLanguageServer]:
with start_ls_context(language) as ls: with start_ls_context(ls_id) as ls:
yield ls yield ls
@@ -134,8 +134,8 @@ def create_default_serena_config():
return SerenaConfig().with_headless_mode_overrides() return SerenaConfig().with_headless_mode_overrides()
def _create_default_project(language: Language, repo_root_override: str | None = None) -> Project: def _create_default_project(ls_id: LanguageServerId, repo_root_override: str | None = None) -> Project:
repo_path = str(get_repo_path(language)) if repo_root_override is None else repo_root_override repo_path = str(get_repo_path(ls_id)) if repo_root_override is None else repo_root_override
return Project.load(repo_path, serena_config=create_default_serena_config()) return Project.load(repo_path, serena_config=create_default_serena_config())
@@ -193,9 +193,9 @@ def language_server(request: LanguageParamRequest):
@contextmanager @contextmanager
def project_context(language: Language, repo_root_override: str | None = None) -> Iterator[Project]: def project_context(ls_id: LanguageServerId, repo_root_override: str | None = None) -> Iterator[Project]:
"""Context manager that creates a Project for the specified language and ensures proper cleanup.""" """Context manager that creates a Project for the specified language and ensures proper cleanup."""
project = _create_default_project(language, repo_root_override) project = _create_default_project(ls_id, repo_root_override)
try: try:
yield project yield project
finally: finally:
@@ -233,16 +233,16 @@ def project(request: LanguageParamRequest, repo_root_override: str | None = None
@contextmanager @contextmanager
def project_with_ls_context(language: Language, repo_root_override: str | None = None) -> Iterator[Project]: def project_with_ls_context(ls_id: LanguageServerId, repo_root_override: str | None = None) -> Iterator[Project]:
"""Context manager that creates a Project with an active language server for the specified language.""" """Context manager that creates a Project with an active language server for the specified language."""
with project_context(language, repo_root_override) as project: with project_context(ls_id, repo_root_override) as project:
project.create_language_server_manager() project.create_language_server_manager()
yield project yield project
@contextmanager @contextmanager
def agent_for_project_context(language: Language, repo_root_override: str | None = None) -> Iterator[SerenaAgent]: def agent_for_project_context(ls_id: LanguageServerId, repo_root_override: str | None = None) -> Iterator[SerenaAgent]:
project_root = str(get_repo_path(language)) if repo_root_override is None else repo_root_override project_root = str(get_repo_path(ls_id)) if repo_root_override is None else repo_root_override
agent = SerenaAgent(project=project_root, serena_config=create_default_serena_config()) agent = SerenaAgent(project=project_root, serena_config=create_default_serena_config())
# wait for agent to be ready # wait for agent to be ready
@@ -273,50 +273,50 @@ is_macos = platform.system() == "Darwin"
is_linux = platform.system() == "Linux" is_linux = platform.system() == "Linux"
_LANGUAGE_PYTEST_MARKERS: dict[Language, list[MarkDecorator | Mark]] = { _LANGUAGE_PYTEST_MARKERS: dict[LanguageServerId, list[MarkDecorator | Mark]] = {
Language.ADA: [pytest.mark.ada], LanguageServerId.ADA: [pytest.mark.ada],
Language.CLOJURE: [pytest.mark.clojure], LanguageServerId.CLOJURE: [pytest.mark.clojure],
Language.CPP: [pytest.mark.cpp], LanguageServerId.CPP: [pytest.mark.cpp],
Language.CPP_CCLS: [pytest.mark.cpp], LanguageServerId.CPP_CCLS: [pytest.mark.cpp],
Language.CUE: [pytest.mark.cue], LanguageServerId.CUE: [pytest.mark.cue],
Language.CSHARP: [pytest.mark.csharp], LanguageServerId.CSHARP: [pytest.mark.csharp],
Language.FSHARP: [pytest.mark.fsharp], LanguageServerId.FSHARP: [pytest.mark.fsharp],
Language.GO: [pytest.mark.go], LanguageServerId.GO: [pytest.mark.go],
Language.HAXE: [pytest.mark.haxe], LanguageServerId.HAXE: [pytest.mark.haxe],
Language.JAVA: [pytest.mark.java], LanguageServerId.JAVA: [pytest.mark.java],
Language.KOTLIN: [pytest.mark.kotlin], LanguageServerId.KOTLIN: [pytest.mark.kotlin],
Language.LEAN4: [pytest.mark.lean4], LanguageServerId.LEAN4: [pytest.mark.lean4],
Language.LATEX: [pytest.mark.latex], LanguageServerId.LATEX: [pytest.mark.latex],
Language.MSL: [pytest.mark.msl], LanguageServerId.MSL: [pytest.mark.msl],
Language.PHP: [pytest.mark.php], LanguageServerId.PHP: [pytest.mark.php],
Language.PHP_PHPACTOR: [pytest.mark.php], LanguageServerId.PHP_PHPACTOR: [pytest.mark.php],
Language.PHP_PHPANTOM: [pytest.mark.php], LanguageServerId.PHP_PHPANTOM: [pytest.mark.php],
Language.POWERSHELL: [pytest.mark.powershell], LanguageServerId.POWERSHELL: [pytest.mark.powershell],
Language.PYTHON: [pytest.mark.python], LanguageServerId.PYTHON: [pytest.mark.python],
Language.PYTHON_JEDI: [pytest.mark.python], LanguageServerId.PYTHON_JEDI: [pytest.mark.python],
Language.PYTHON_TY: [pytest.mark.python], LanguageServerId.PYTHON_TY: [pytest.mark.python],
Language.PYTHON_PYREFLY: [pytest.mark.python], LanguageServerId.PYTHON_PYREFLY: [pytest.mark.python],
Language.PYTHON_BASEDPYRIGHT: [pytest.mark.python], LanguageServerId.PYTHON_BASEDPYRIGHT: [pytest.mark.python],
Language.RUST: [pytest.mark.rust], LanguageServerId.RUST: [pytest.mark.rust],
Language.TYPESCRIPT: [pytest.mark.typescript], LanguageServerId.TYPESCRIPT: [pytest.mark.typescript],
Language.BSL: [pytest.mark.bsl], LanguageServerId.BSL: [pytest.mark.bsl],
Language.SVELTE: [pytest.mark.svelte], LanguageServerId.SVELTE: [pytest.mark.svelte],
Language.ANGULAR: [pytest.mark.angular], LanguageServerId.ANGULAR: [pytest.mark.angular],
Language.HTML: [pytest.mark.html], LanguageServerId.HTML: [pytest.mark.html],
Language.SCSS: [pytest.mark.scss], LanguageServerId.SCSS: [pytest.mark.scss],
} }
def get_pytest_markers(language: Language) -> list[MarkDecorator | Mark]: def get_pytest_markers(ls_id: LanguageServerId) -> list[MarkDecorator | Mark]:
"""Pytest markers for a language. """Pytest markers for a language server.
Returns the primary language marker plus the central enablement skip derived from Returns the primary language server marker plus the central enablement skip derived from
``language_tests_enabled()`` -- so per-language availability/reliability lives in exactly one ``language_tests_enabled()`` -- so per-language availability/reliability lives in exactly one
place (``_determine_disabled_languages``) instead of being duplicated per marker or per test file. place (``_determine_disabled_languages``) instead of being duplicated per marker or per test file.
""" """
return [ return [
*_LANGUAGE_PYTEST_MARKERS[language], *_LANGUAGE_PYTEST_MARKERS[ls_id],
pytest.mark.skipif(not language_tests_enabled(language), reason=f"{language.value} tests are disabled in this environment"), pytest.mark.skipif(not language_server_tests_enabled(ls_id), reason=f"{ls_id.value} tests are disabled in this environment"),
] ]
@@ -395,9 +395,9 @@ def _is_ocaml_lsp_available() -> bool:
return False return False
def _determine_disabled_languages() -> list[Language]: def _determine_disabled_language_servers() -> list[LanguageServerId]:
""" """
Determine which language tests are disabled in the current environment. Determine which language server tests are disabled in the current environment.
Every language falls into exactly ONE of the categories below; a language that is not appended Every language falls into exactly ONE of the categories below; a language that is not appended
here is **category 4 (enabled everywhere)**, e.g. python, typescript, go, java, kotlin-locally. here is **category 4 (enabled everywhere)**, e.g. python, typescript, go, java, kotlin-locally.
@@ -412,112 +412,112 @@ def _determine_disabled_languages() -> list[Language]:
4. ENABLED EVERYWHERE -- not listed in this function at all. 4. ENABLED EVERYWHERE -- not listed in this function at all.
5. DISABLED ONLY ON CI (resource/stability reasons) even though the precondition holds locally. 5. DISABLED ONLY ON CI (resource/stability reasons) even though the precondition holds locally.
""" """
result: list[Language] = [] result: list[LanguageServerId] = []
# === 1. Always disabled (flaky / broken everywhere) === # === 1. Always disabled (flaky / broken everywhere) ===
result.append(Language.BSL) # 1C:Enterprise; niche and the tests are slow and flaky result.append(LanguageServerId.BSL) # 1C:Enterprise; niche and the tests are slow and flaky
result.append(Language.FSHARP) # F# language server is currently unreliable result.append(LanguageServerId.FSHARP) # F# language server is currently unreliable
# === 2. Disabled off-CI if the precondition is missing; expected to be present on CI === # === 2. Disabled off-CI if the precondition is missing; expected to be present on CI ===
if _sh.which("terraform") is None and not is_ci: if _sh.which("terraform") is None and not is_ci:
result.append(Language.TERRAFORM) result.append(LanguageServerId.TERRAFORM)
if _sh.which("regal") is None and not is_ci: if _sh.which("regal") is None and not is_ci:
result.append(Language.REGO) result.append(LanguageServerId.REGO)
if _sh.which("elm") is None and not is_ci: if _sh.which("elm") is None and not is_ci:
result.append(Language.ELM) result.append(LanguageServerId.ELM)
# qmlls is installed (standalone build; see pytest.yml) only on the Ubuntu other-langs CI batch. It is # qmlls is installed (standalone build; see pytest.yml) only on the Ubuntu other-langs CI batch. It is
# expected there, so a missing binary on Linux CI is NOT skipped here -- the test runs and fails loudly, # expected there, so a missing binary on Linux CI is NOT skipped here -- the test runs and fails loudly,
# catching a CI setup regression. On Windows/macOS CI (never installed) and off-CI without the binary it skips. # catching a CI setup regression. On Windows/macOS CI (never installed) and off-CI without the binary it skips.
if (_sh.which("qmlls6") is None and _sh.which("qmlls") is None) and not (is_ci and is_linux): if (_sh.which("qmlls6") is None and _sh.which("qmlls") is None) and not (is_ci and is_linux):
result.append(Language.QML) result.append(LanguageServerId.QML)
# === 3. Disabled wherever the precondition is missing (including on CI) === # === 3. Disabled wherever the precondition is missing (including on CI) ===
# 3a. Platform precondition: these language servers have no native Windows support. # 3a. Platform precondition: these language servers have no native Windows support.
if is_windows: if is_windows:
result.append(Language.ANSIBLE) # ansible-language-server has no native Windows support result.append(LanguageServerId.ANSIBLE) # ansible-language-server has no native Windows support
if not is_macos: if not is_macos:
result.append(Language.SWIFT) # swiftly toolchain is only set up on the macOS native batch result.append(LanguageServerId.SWIFT) # swiftly toolchain is only set up on the macOS native batch
# 3b. Toolchain / language-server availability (the LS/compiler must be on PATH or installed). # 3b. Toolchain / language-server availability (the LS/compiler must be on PATH or installed).
if _sh.which("clangd") is None: if _sh.which("clangd") is None:
result.append(Language.CPP) result.append(LanguageServerId.CPP)
if _sh.which("ccls") is None or is_windows: # no recent ccls binary is available for Windows if _sh.which("ccls") is None or is_windows: # no recent ccls binary is available for Windows
result.append(Language.CPP_CCLS) result.append(LanguageServerId.CPP_CCLS)
if _sh.which("php") is None: if _sh.which("php") is None:
result.append(Language.PHP_PHPACTOR) result.append(LanguageServerId.PHP_PHPACTOR)
result.append(Language.PHP_PHPANTOM) result.append(LanguageServerId.PHP_PHPANTOM)
if not is_clojure_cli_available(): if not is_clojure_cli_available():
result.append(Language.CLOJURE) result.append(LanguageServerId.CLOJURE)
if _sh.which("verible-verilog-ls") is None: if _sh.which("verible-verilog-ls") is None:
result.append(Language.SYSTEMVERILOG) result.append(LanguageServerId.SYSTEMVERILOG)
if not _is_matlab_available(): if not _is_matlab_available():
result.append(Language.MATLAB) result.append(LanguageServerId.MATLAB)
if ERLANG_LS_UNAVAILABLE: # no Erlang-OTP / no rebar3 / Windows -- see test/solidlsp/erlang if ERLANG_LS_UNAVAILABLE: # no Erlang-OTP / no rebar3 / Windows -- see test/solidlsp/erlang
result.append(Language.ERLANG) result.append(LanguageServerId.ERLANG)
if EXPERT_UNAVAILABLE: # Elixir not installed -- see test/solidlsp/elixir if EXPERT_UNAVAILABLE: # Elixir not installed -- see test/solidlsp/elixir
result.append(Language.ELIXIR) result.append(LanguageServerId.ELIXIR)
if _sh.which("lean") is None: if _sh.which("lean") is None:
result.append(Language.LEAN4) result.append(LanguageServerId.LEAN4)
if _sh.which("crystalline") is None: if _sh.which("crystalline") is None:
result.append(Language.CRYSTAL) result.append(LanguageServerId.CRYSTAL)
if _sh.which("julia") is None: # LanguageServer.jl is auto-installed by the LS when julia is present if _sh.which("julia") is None: # LanguageServer.jl is auto-installed by the LS when julia is present
result.append(Language.JULIA) result.append(LanguageServerId.JULIA)
if _sh.which("nixd") is None: if _sh.which("nixd") is None:
result.append(Language.NIX) result.append(LanguageServerId.NIX)
if _sh.which("haskell-language-server-wrapper") is None: if _sh.which("haskell-language-server-wrapper") is None:
result.append(Language.HASKELL) result.append(LanguageServerId.HASKELL)
if not _is_r_language_server_available(): # `which("R")` isn't enough -- needs the languageserver package if not _is_r_language_server_available(): # `which("R")` isn't enough -- needs the languageserver package
result.append(Language.R) result.append(LanguageServerId.R)
if not _is_ocaml_lsp_available(): # opam alone isn't enough -- needs the ocaml-lsp-server package if not _is_ocaml_lsp_available(): # opam alone isn't enough -- needs the ocaml-lsp-server package
result.append(Language.OCAML) result.append(LanguageServerId.OCAML)
if not _is_perl_language_server_available(): # perl ships with the OS; the LS module is the real signal if not _is_perl_language_server_available(): # perl ships with the OS; the LS module is the real signal
result.append(Language.PERL) result.append(LanguageServerId.PERL)
# === 4. Enabled everywhere: every language NOT listed in this function (python, go, java, ...) === # === 4. Enabled everywhere: every language NOT listed in this function (python, go, java, ...) ===
# === 5. Disabled only on CI (works locally; too unstable/costly on the CI runners) === # === 5. Disabled only on CI (works locally; too unstable/costly on the CI runners) ===
if is_ci: if is_ci:
result.append(Language.KOTLIN) # IntelliJ-based Kotlin LSP crashes on JVM restart under CI memory limits result.append(LanguageServerId.KOTLIN) # IntelliJ-based Kotlin LSP crashes on JVM restart under CI memory limits
return result return result
_disabled_languages = _determine_disabled_languages() _disabled_language_servers = _determine_disabled_language_servers()
def language_tests_enabled(language: Language) -> bool: def language_server_tests_enabled(ls_id: LanguageServerId) -> bool:
""" """
Check if tests for the given language are enabled in the current environment. Check if tests for the given language server are enabled in the current environment.
:param language: the language to check :param ls_id: the language server to check
:return: True if tests for the language are enabled, False otherwise :return: True if tests for the language are enabled, False otherwise
""" """
return language not in _disabled_languages return ls_id not in _disabled_language_servers
def language_supports_implementation(language: Language) -> bool: def ls_supports_implementation(language: LanguageServerId) -> bool:
return language.supports_implementation_request() return language.supports_implementation_request()
def languages_supporting_implementation(*languages: Language) -> list[Language]: def language_servers_supporting_implementation(*languages: LanguageServerId) -> list[LanguageServerId]:
return [language for language in languages if language_supports_implementation(language)] return [language for language in languages if ls_supports_implementation(language)]
_VERIFIED_IMPLEMENTATION_LANGUAGES = { _VERIFIED_IMPLEMENTATION_LANGUAGES = {
Language.ANGULAR, LanguageServerId.ANGULAR,
Language.CSHARP, LanguageServerId.CSHARP,
Language.GO, LanguageServerId.GO,
Language.JAVA, LanguageServerId.JAVA,
Language.RUST, LanguageServerId.RUST,
Language.TYPESCRIPT, LanguageServerId.TYPESCRIPT,
} }
def language_has_verified_implementation_support(language: Language) -> bool: def ls_has_verified_implementation_support(language: LanguageServerId) -> bool:
""" """
True only for languages where the server advertises implementation support and True only for languages where the server advertises implementation support and
the repo fixtures contain a verified working go-to-implementation scenario. the repo fixtures contain a verified working go-to-implementation scenario.
""" """
return language in _VERIFIED_IMPLEMENTATION_LANGUAGES and language_supports_implementation(language) return language in _VERIFIED_IMPLEMENTATION_LANGUAGES and ls_supports_implementation(language)
def find_identifier_position(file_path: Path, identifier: str) -> tuple[int, int] | None: def find_identifier_position(file_path: Path, identifier: str) -> tuple[int, int] | None:
@@ -5,7 +5,7 @@ from pathlib import Path
from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig
from serena.project import Project from serena.project import Project
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
def _create_test_project( def _create_test_project(
@@ -16,7 +16,7 @@ def _create_test_project(
"""Helper to create a Project with the given ignored paths configuration.""" """Helper to create a Project with the given ignored paths configuration."""
config = ProjectConfig( config = ProjectConfig(
project_name="test_project", project_name="test_project",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
ignored_paths=project_ignored_paths or [], ignored_paths=project_ignored_paths or [],
ignore_all_files_in_gitignore=False, ignore_all_files_in_gitignore=False,
) )
@@ -145,7 +145,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
"""RegisteredProject.get_project_instance() passes global_ignored_paths to Project.""" """RegisteredProject.get_project_instance() passes global_ignored_paths to Project."""
config = ProjectConfig( config = ProjectConfig(
project_name="test_project", project_name="test_project",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
ignored_paths=[], ignored_paths=[],
ignore_all_files_in_gitignore=False, ignore_all_files_in_gitignore=False,
) )
@@ -161,7 +161,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
"""RegisteredProject without global_ignored_paths defaults to empty.""" """RegisteredProject without global_ignored_paths defaults to empty."""
config = ProjectConfig( config = ProjectConfig(
project_name="test_project", project_name="test_project",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
ignored_paths=[], ignored_paths=[],
ignore_all_files_in_gitignore=False, ignore_all_files_in_gitignore=False,
) )
@@ -193,7 +193,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
"""RegisteredProject.from_project_instance() threads global_ignored_paths to Project.""" """RegisteredProject.from_project_instance() threads global_ignored_paths to Project."""
config = ProjectConfig( config = ProjectConfig(
project_name="test_project", project_name="test_project",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
ignored_paths=[], ignored_paths=[],
ignore_all_files_in_gitignore=False, ignore_all_files_in_gitignore=False,
) )
@@ -233,7 +233,7 @@ class TestGlobalIgnoredPathsWithGitignore:
"""Global patterns, project patterns, and .gitignore patterns are all applied together.""" """Global patterns, project patterns, and .gitignore patterns are all applied together."""
config = ProjectConfig( config = ProjectConfig(
project_name="test_project", project_name="test_project",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
ignored_paths=["build"], ignored_paths=["build"],
ignore_all_files_in_gitignore=True, ignore_all_files_in_gitignore=True,
) )
+19 -19
View File
@@ -18,7 +18,7 @@ from serena.config.serena_config import (
) )
from serena.constants import PROJECT_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME from serena.constants import PROJECT_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME
from serena.project import MemoryManager, Project from serena.project import MemoryManager, Project
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import create_default_serena_config from test.conftest import create_default_serena_config
@@ -62,7 +62,7 @@ class TestProjectConfigAutogenerate:
# Verify the configuration # Verify the configuration
assert config.project_name == self.project_path.name assert config.project_name == self.project_path.name
assert config.languages == [Language.PYTHON] assert config.languages == [LanguageServerId.PYTHON]
def test_autogenerate_with_python_files_and_custom_ls_priorities(self): def test_autogenerate_with_python_files_and_custom_ls_priorities(self):
"""Test successful autogeneration with Python source files, using custom language server priorities.""" """Test successful autogeneration with Python source files, using custom language server priorities."""
@@ -71,14 +71,14 @@ class TestProjectConfigAutogenerate:
python_file.write_text("def hello():\n print('Hello, world!')\n") python_file.write_text("def hello():\n print('Hello, world!')\n")
serena_config = deepcopy(self.serena_config) serena_config = deepcopy(self.serena_config)
serena_config.ls_priorities = {Language.PYTHON_TY.value: 3} serena_config.ls_priorities = {LanguageServerId.PYTHON_TY.value: 3}
# Run autogenerate # Run autogenerate
config = ProjectConfig.autogenerate(self.project_path, serena_config, save_to_disk=False) config = ProjectConfig.autogenerate(self.project_path, serena_config, save_to_disk=False)
# Verify the configuration # Verify the configuration
assert config.project_name == self.project_path.name assert config.project_name == self.project_path.name
assert config.languages == [Language.PYTHON_TY] assert config.languages == [LanguageServerId.PYTHON_TY]
def test_autogenerate_with_js_files(self): def test_autogenerate_with_js_files(self):
"""Test successful autogeneration with JavaScript source files.""" """Test successful autogeneration with JavaScript source files."""
@@ -88,7 +88,7 @@ class TestProjectConfigAutogenerate:
# Run autogenerate - should pick Python as dominant # Run autogenerate - should pick Python as dominant
config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False) config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
assert config.languages == [Language.TYPESCRIPT] assert config.languages == [LanguageServerId.TYPESCRIPT]
def test_autogenerate_with_multiple_languages(self): def test_autogenerate_with_multiple_languages(self):
"""Test autogeneration picks dominant language when multiple are present.""" """Test autogeneration picks dominant language when multiple are present."""
@@ -100,7 +100,7 @@ class TestProjectConfigAutogenerate:
# Run autogenerate - should pick Python as dominant # Run autogenerate - should pick Python as dominant
config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False) config = ProjectConfig.autogenerate(self.project_path, self.serena_config, save_to_disk=False)
assert config.languages == [Language.PYTHON] assert config.languages == [LanguageServerId.PYTHON]
def test_autogenerate_saves_to_disk(self): def test_autogenerate_saves_to_disk(self):
"""Test that autogenerate can save the configuration to disk.""" """Test that autogenerate can save the configuration to disk."""
@@ -116,7 +116,7 @@ class TestProjectConfigAutogenerate:
assert config_path.exists() assert config_path.exists()
# Verify the content # Verify the content
assert config.languages == [Language.GO] assert config.languages == [LanguageServerId.GO]
def test_autogenerate_nonexistent_path(self): def test_autogenerate_nonexistent_path(self):
"""Test that autogenerate raises FileNotFoundError for non-existent path.""" """Test that autogenerate raises FileNotFoundError for non-existent path."""
@@ -153,7 +153,7 @@ class TestProjectConfigAutogenerate:
config = ProjectConfig.autogenerate(self.project_path, self.serena_config, project_name=custom_name, save_to_disk=False) config = ProjectConfig.autogenerate(self.project_path, self.serena_config, project_name=custom_name, save_to_disk=False)
assert config.project_name == custom_name assert config.project_name == custom_name
assert config.languages == [Language.TYPESCRIPT] assert config.languages == [LanguageServerId.TYPESCRIPT]
class TestProjectConfig: class TestProjectConfig:
@@ -168,14 +168,14 @@ class TestProjectConfigLanguageBackend:
def test_language_backend_defaults_to_none(self): def test_language_backend_defaults_to_none(self):
config = ProjectConfig( config = ProjectConfig(
project_name="test", project_name="test",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
) )
assert config.language_backend is None assert config.language_backend is None
def test_language_backend_can_be_set(self): def test_language_backend_can_be_set(self):
config = ProjectConfig( config = ProjectConfig(
project_name="test", project_name="test",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
language_backend=LanguageBackend.JETBRAINS, language_backend=LanguageBackend.JETBRAINS,
) )
assert config.language_backend == LanguageBackend.JETBRAINS assert config.language_backend == LanguageBackend.JETBRAINS
@@ -183,7 +183,7 @@ class TestProjectConfigLanguageBackend:
def test_language_backend_roundtrips_through_yaml(self): def test_language_backend_roundtrips_through_yaml(self):
config = ProjectConfig( config = ProjectConfig(
project_name="test", project_name="test",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
language_backend=LanguageBackend.JETBRAINS, language_backend=LanguageBackend.JETBRAINS,
) )
d = config._to_yaml_dict() d = config._to_yaml_dict()
@@ -192,7 +192,7 @@ class TestProjectConfigLanguageBackend:
def test_language_backend_none_roundtrips_through_yaml(self): def test_language_backend_none_roundtrips_through_yaml(self):
config = ProjectConfig( config = ProjectConfig(
project_name="test", project_name="test",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
) )
d = config._to_yaml_dict() d = config._to_yaml_dict()
assert d["language_backend"] is None assert d["language_backend"] is None
@@ -232,7 +232,7 @@ def _make_config_with_project(
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
project_config=ProjectConfig( project_config=ProjectConfig(
project_name=project_name, project_name=project_name,
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
language_backend=language_backend, language_backend=language_backend,
), ),
serena_config=config, serena_config=config,
@@ -286,7 +286,7 @@ class TestEffectiveLanguageBackend:
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "java" / "test_repo"), project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "java" / "test_repo"),
project_config=ProjectConfig( project_config=ProjectConfig(
project_name="jb_proj", project_name="jb_proj",
languages=[Language.JAVA], languages=[LanguageServerId.JAVA],
language_backend=LanguageBackend.JETBRAINS, language_backend=LanguageBackend.JETBRAINS,
), ),
serena_config=config, serena_config=config,
@@ -309,7 +309,7 @@ class TestEffectiveLanguageBackend:
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
project_config=ProjectConfig( project_config=ProjectConfig(
project_name="lsp_proj2", project_name="lsp_proj2",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
language_backend=LanguageBackend.LSP, language_backend=LanguageBackend.LSP,
), ),
serena_config=config, serena_config=config,
@@ -332,7 +332,7 @@ class TestEffectiveLanguageBackend:
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"), project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
project_config=ProjectConfig( project_config=ProjectConfig(
project_name="proj2", project_name="proj2",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
language_backend=None, language_backend=None,
), ),
serena_config=config, serena_config=config,
@@ -432,7 +432,7 @@ class TestProjectSerenaDataFolder:
def _make_project(self, serena_config: "SerenaConfig | None" = None) -> Project: def _make_project(self, serena_config: "SerenaConfig | None" = None) -> Project:
project_config = ProjectConfig( project_config = ProjectConfig(
project_name="myproject", project_name="myproject",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
) )
project = Project( project = Project(
project_root=str(self.project_path), project_root=str(self.project_path),
@@ -679,11 +679,11 @@ class TestProjectConfigActivationCommand:
return data return data
def test_activation_command_defaults_to_none(self): def test_activation_command_defaults_to_none(self):
config = ProjectConfig(project_name="test", languages=[Language.PYTHON]) config = ProjectConfig(project_name="test", languages=[LanguageServerId.PYTHON])
assert config.activation_command is None assert config.activation_command is None
def test_activation_command_timeout_default(self): def test_activation_command_timeout_default(self):
config = ProjectConfig(project_name="test", languages=[Language.PYTHON]) config = ProjectConfig(project_name="test", languages=[LanguageServerId.PYTHON])
assert config.activation_command_timeout == 180.0 assert config.activation_command_timeout == 180.0
def test_activation_command_parsed_from_dict(self): def test_activation_command_parsed_from_dict(self):
+2 -2
View File
@@ -8,7 +8,7 @@ import pytest
from serena.config.serena_config import ProjectConfig, SerenaConfig from serena.config.serena_config import ProjectConfig, SerenaConfig
from serena.project import Project from serena.project import Project
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
def _make_project( def _make_project(
@@ -25,7 +25,7 @@ def _make_project(
) )
project_config = ProjectConfig( project_config = ProjectConfig(
project_name="test-activation", project_name="test-activation",
languages=[Language.PYTHON], languages=[LanguageServerId.PYTHON],
activation_command=activation_command, activation_command=activation_command,
activation_command_timeout=activation_command_timeout, activation_command_timeout=activation_command_timeout,
) )
+7 -7
View File
@@ -2,7 +2,7 @@ from collections.abc import Callable
from types import SimpleNamespace from types import SimpleNamespace
from serena.dashboard import SerenaDashboardAPI from serena.dashboard import SerenaDashboardAPI
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
class _DummyMemoryLogHandler: class _DummyMemoryLogHandler:
@@ -28,7 +28,7 @@ class _DummyAgent:
return self._project return self._project
def _make_dashboard(project_languages: list[Language] | None) -> SerenaDashboardAPI: def _make_dashboard(project_languages: list[LanguageServerId] | None) -> SerenaDashboardAPI:
project = None project = None
if project_languages is not None: if project_languages is not None:
project = SimpleNamespace(project_config=SimpleNamespace(languages=project_languages)) project = SimpleNamespace(project_config=SimpleNamespace(languages=project_languages))
@@ -39,15 +39,15 @@ def _make_dashboard(project_languages: list[Language] | None) -> SerenaDashboard
def test_available_languages_include_experimental_when_no_active_project(): def test_available_languages_include_experimental_when_no_active_project():
dashboard = _make_dashboard(project_languages=None) dashboard = _make_dashboard(project_languages=None)
response = dashboard._get_available_languages() response = dashboard._get_available_languages()
expected = sorted(lang.value for lang in Language.iter_all(include_experimental=True)) expected = sorted(lang.value for lang in LanguageServerId.iter_all(include_experimental=True))
assert response.languages == expected assert response.languages == expected
def test_available_languages_exclude_project_languages(): def test_available_languages_exclude_project_languages():
dashboard = _make_dashboard(project_languages=[Language.PYTHON, Language.MARKDOWN]) dashboard = _make_dashboard(project_languages=[LanguageServerId.PYTHON, LanguageServerId.MARKDOWN])
response = dashboard._get_available_languages() response = dashboard._get_available_languages()
available = set(response.languages) available = set(response.languages)
assert Language.PYTHON.value not in available assert LanguageServerId.PYTHON.value not in available
assert Language.MARKDOWN.value not in available assert LanguageServerId.MARKDOWN.value not in available
# ensure experimental languages remain available for selection # ensure experimental languages remain available for selection
assert Language.ANSIBLE.value in available assert LanguageServerId.ANSIBLE.value in available
+3 -3
View File
@@ -14,7 +14,7 @@ import pytest
from serena.agent import SerenaAgent from serena.agent import SerenaAgent
from serena.project import Project from serena.project import Project
from serena.tools import FindReferencingSymbolsTool from serena.tools import FindReferencingSymbolsTool
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import agent_for_project_context, get_repo_path from test.conftest import agent_for_project_context, get_repo_path
pytestmark = pytest.mark.python pytestmark = pytest.mark.python
@@ -73,10 +73,10 @@ class FileSystemSyncTestCase:
def run(self, tmp_path): def run(self, tmp_path):
# Work on an isolated copy so we can freely create/edit/delete files under the project root. # Work on an isolated copy so we can freely create/edit/delete files under the project root.
repo_root = tmp_path / "repo" repo_root = tmp_path / "repo"
shutil.copytree(get_repo_path(Language.PYTHON), repo_root) shutil.copytree(get_repo_path(LanguageServerId.PYTHON), repo_root)
caller_abs = repo_root / self._CALLER_REL_PATH caller_abs = repo_root / self._CALLER_REL_PATH
with agent_for_project_context(Language.PYTHON, str(repo_root)) as agent: with agent_for_project_context(LanguageServerId.PYTHON, str(repo_root)) as agent:
project = agent.get_active_project_or_raise() project = agent.get_active_project_or_raise()
# Warm the reference index, then establish the freshness baseline (first poll never notifies). # Warm the reference index, then establish the freshness baseline (first poll never notifies).
+126 -121
View File
@@ -32,24 +32,24 @@ from serena.tools import (
SafeDeleteSymbol, SafeDeleteSymbol,
Tool, Tool,
) )
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.conftest import ( from test.conftest import (
find_identifier_pos, find_identifier_pos,
get_pytest_markers, get_pytest_markers,
get_repo_path, get_repo_path,
language_tests_enabled, language_server_tests_enabled,
) )
from test.serena.config.test_context_mode import GROK_EXCLUDED_TOOLS from test.serena.config.test_context_mode import GROK_EXCLUDED_TOOLS
@dataclass @dataclass
class BaseCase: class BaseCase:
language: Language ls_id: LanguageServerId
id: str id: str
def to_pytest_param(self, *marks: MarkDecorator | Mark) -> ParameterSet: def to_pytest_param(self, *marks: MarkDecorator | Mark) -> ParameterSet:
return pytest.param(self.language, self, marks=[*get_pytest_markers(self.language), *marks], id=self.id) return pytest.param(self.ls_id, self, marks=[*get_pytest_markers(self.ls_id), *marks], id=self.id)
@dataclass @dataclass
@@ -139,7 +139,6 @@ class SafeDeleteCase(BaseCase):
@dataclass @dataclass
class DiagnosticCase(BaseCase): class DiagnosticCase(BaseCase):
language: Language
relative_path: str relative_path: str
name_path1: str name_path1: str
name_path2: str | None name_path2: str | None
@@ -189,8 +188,8 @@ class DiagnosticCase(BaseCase):
DIAGNOSTIC_CASES = [ DIAGNOSTIC_CASES = [
DiagnosticCase( DiagnosticCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id=f"{Language.PYTHON.value}_missing_user", id=f"{LanguageServerId.PYTHON.value}_missing_user",
relative_path=os.path.join("test_repo", "diagnostics_sample.py"), relative_path=os.path.join("test_repo", "diagnostics_sample.py"),
name_path1="broken_factory", name_path1="broken_factory",
name_path2="broken_consumer", name_path2="broken_consumer",
@@ -198,8 +197,8 @@ DIAGNOSTIC_CASES = [
message_fragment2="undefined_name", message_fragment2="undefined_name",
).to_pytest_param(), ).to_pytest_param(),
DiagnosticCase( DiagnosticCase(
language=Language.CLOJURE, ls_id=LanguageServerId.CLOJURE,
id=f"{Language.CLOJURE.value}_missing-greeting", id=f"{LanguageServerId.CLOJURE.value}_missing-greeting",
relative_path=os.path.join("src", "test_app", "diagnostics_sample.clj"), relative_path=os.path.join("src", "test_app", "diagnostics_sample.clj"),
name_path1="broken-factory", name_path1="broken-factory",
name_path2="broken-consumer", name_path2="broken-consumer",
@@ -207,8 +206,8 @@ DIAGNOSTIC_CASES = [
message_fragment2="missing-consumer-value", message_fragment2="missing-consumer-value",
).to_pytest_param(), ).to_pytest_param(),
DiagnosticCase( DiagnosticCase(
language=Language.GO, ls_id=LanguageServerId.GO,
id=f"{Language.GO.value}_missingGreeting", id=f"{LanguageServerId.GO.value}_missingGreeting",
relative_path="diagnostics_sample.go", relative_path="diagnostics_sample.go",
name_path1="brokenFactory", name_path1="brokenFactory",
name_path2="brokenConsumer", name_path2="brokenConsumer",
@@ -216,8 +215,8 @@ DIAGNOSTIC_CASES = [
message_fragment2="missingConsumerValue", message_fragment2="missingConsumerValue",
).to_pytest_param(), ).to_pytest_param(),
DiagnosticCase( DiagnosticCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id=f"{Language.TYPESCRIPT.value}_missingGreeting", id=f"{LanguageServerId.TYPESCRIPT.value}_missingGreeting",
relative_path="diagnostics_sample.ts", relative_path="diagnostics_sample.ts",
name_path1="brokenFactory", name_path1="brokenFactory",
name_path2="brokenConsumer", name_path2="brokenConsumer",
@@ -229,7 +228,7 @@ DIAGNOSTIC_CASES = [
FIND_DEFINING_SYMBOL_CASES = [ FIND_DEFINING_SYMBOL_CASES = [
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_user_in_services", id="python_user_in_services",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
identifier="User", identifier="User",
@@ -239,7 +238,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="models.py", expected_definition_file="models.py",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.PYTHON_TY, ls_id=LanguageServerId.PYTHON_TY,
id="python_ty_user_in_services", id="python_ty_user_in_services",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
identifier="User", identifier="User",
@@ -249,7 +248,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="models.py", expected_definition_file="models.py",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.GO, ls_id=LanguageServerId.GO,
id="go_helper_in_main", id="go_helper_in_main",
relative_path="main.go", relative_path="main.go",
identifier="Helper", identifier="Helper",
@@ -259,7 +258,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="main.go", expected_definition_file="main.go",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.JAVA, ls_id=LanguageServerId.JAVA,
id="java_model_in_main", id="java_model_in_main",
relative_path=os.path.join("src", "main", "java", "test_repo", "Main.java"), relative_path=os.path.join("src", "main", "java", "test_repo", "Main.java"),
identifier="Model", identifier="Model",
@@ -269,7 +268,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="Model.java", expected_definition_file="Model.java",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.KOTLIN, ls_id=LanguageServerId.KOTLIN,
id="kotlin_model_in_main", id="kotlin_model_in_main",
relative_path=os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"), relative_path=os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
identifier="Model", identifier="Model",
@@ -279,7 +278,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="Model.kt", expected_definition_file="Model.kt",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.RUST, ls_id=LanguageServerId.RUST,
id="rust_format_greeting", id="rust_format_greeting",
relative_path=os.path.join("src", "main.rs"), relative_path=os.path.join("src", "main.rs"),
identifier="format_greeting", identifier="format_greeting",
@@ -289,7 +288,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="lib.rs", expected_definition_file="lib.rs",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.PHP, ls_id=LanguageServerId.PHP,
id="php_helper_function", id="php_helper_function",
relative_path="index.php", relative_path="index.php",
identifier="helperFunction", identifier="helperFunction",
@@ -299,7 +298,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="helper.php", expected_definition_file="helper.php",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.CLOJURE, ls_id=LanguageServerId.CLOJURE,
id="clojure_multiply_in_utils", id="clojure_multiply_in_utils",
relative_path=os.path.join("src", "test_app", "utils.clj"), relative_path=os.path.join("src", "test_app", "utils.clj"),
identifier="multiply", identifier="multiply",
@@ -309,7 +308,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file=os.path.join("src", "test_app", "core.clj"), expected_definition_file=os.path.join("src", "test_app", "core.clj"),
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.CSHARP, ls_id=LanguageServerId.CSHARP,
id="csharp_add_in_program", id="csharp_add_in_program",
relative_path="Program.cs", relative_path="Program.cs",
identifier="Add", identifier="Add",
@@ -319,7 +318,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="Program.cs", expected_definition_file="Program.cs",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.POWERSHELL, ls_id=LanguageServerId.POWERSHELL,
id="powershell_convert_to_uppercase", id="powershell_convert_to_uppercase",
relative_path="main.ps1", relative_path="main.ps1",
identifier="Convert-ToUpperCase", identifier="Convert-ToUpperCase",
@@ -329,7 +328,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="utils.ps1", expected_definition_file="utils.ps1",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.CPP, ls_id=LanguageServerId.CPP,
id="cpp_add_in_a", id="cpp_add_in_a",
relative_path="a.cpp", relative_path="a.cpp",
identifier="add", identifier="add",
@@ -339,7 +338,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="b.cpp", expected_definition_file="b.cpp",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.LEAN4, ls_id=LanguageServerId.LEAN4,
id="lean_add_in_main", id="lean_add_in_main",
relative_path="Main.lean", relative_path="Main.lean",
identifier="add", identifier="add",
@@ -349,7 +348,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="Helper.lean", expected_definition_file="Helper.lean",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id="typescript_helper_function", id="typescript_helper_function",
relative_path="index.ts", relative_path="index.ts",
identifier="helperFunction", identifier="helperFunction",
@@ -359,7 +358,7 @@ FIND_DEFINING_SYMBOL_CASES = [
expected_definition_file="index.ts", expected_definition_file="index.ts",
).to_pytest_param(), ).to_pytest_param(),
FindDefiningSymbolCase( FindDefiningSymbolCase(
language=Language.FSHARP, ls_id=LanguageServerId.FSHARP,
id="fsharp_add_in_program", id="fsharp_add_in_program",
relative_path="Program.fs", relative_path="Program.fs",
identifier="add", identifier="add",
@@ -374,7 +373,7 @@ FIND_DEFINING_SYMBOL_CASES = [
FIND_DEFINING_SYMBOL_REGEX_CASES = [ FIND_DEFINING_SYMBOL_REGEX_CASES = [
RegexDefiningSymbolCase( RegexDefiningSymbolCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_import_user", id="python_import_user",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
regex=r"from \.models import Item, (User)", regex=r"from \.models import Item, (User)",
@@ -383,7 +382,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
expected_definition_file="models.py", expected_definition_file="models.py",
).to_pytest_param(), ).to_pytest_param(),
RegexDefiningSymbolCase( RegexDefiningSymbolCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_create_user_call", id="python_create_user_call",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
regex=r"=\s+(User)\(", regex=r"=\s+(User)\(",
@@ -392,7 +391,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
expected_definition_file="models.py", expected_definition_file="models.py",
).to_pytest_param(), ).to_pytest_param(),
RegexDefiningSymbolCase( RegexDefiningSymbolCase(
language=Language.PYTHON_TY, ls_id=LanguageServerId.PYTHON_TY,
id="python_ty_create_user_call", id="python_ty_create_user_call",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
regex=r"=\s+(User)\(", regex=r"=\s+(User)\(",
@@ -401,7 +400,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
expected_definition_file="models.py", expected_definition_file="models.py",
).to_pytest_param(), ).to_pytest_param(),
RegexDefiningSymbolCase( RegexDefiningSymbolCase(
language=Language.GO, ls_id=LanguageServerId.GO,
id="go_greeter_var", id="go_greeter_var",
relative_path="main.go", relative_path="main.go",
regex=r"var greeter (Greeter) =", regex=r"var greeter (Greeter) =",
@@ -413,7 +412,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
FIND_IMPLEMENTATION_CASES = [ FIND_IMPLEMENTATION_CASES = [
FindImplementationCase( FindImplementationCase(
language=Language.CSHARP, ls_id=LanguageServerId.CSHARP,
id="csharp_greeter_format", id="csharp_greeter_format",
symbol_name="IGreeter/FormatGreeting", symbol_name="IGreeter/FormatGreeting",
definition_file=os.path.join("Services", "IGreeter.cs"), definition_file=os.path.join("Services", "IGreeter.cs"),
@@ -421,7 +420,7 @@ FIND_IMPLEMENTATION_CASES = [
expected_symbol_name="FormatGreeting", expected_symbol_name="FormatGreeting",
).to_pytest_param(), ).to_pytest_param(),
FindImplementationCase( FindImplementationCase(
language=Language.GO, ls_id=LanguageServerId.GO,
id="go_greeter_format", id="go_greeter_format",
symbol_name="Greeter/FormatGreeting", symbol_name="Greeter/FormatGreeting",
definition_file="main.go", definition_file="main.go",
@@ -429,7 +428,7 @@ FIND_IMPLEMENTATION_CASES = [
expected_symbol_name="FormatGreeting", expected_symbol_name="FormatGreeting",
).to_pytest_param(), ).to_pytest_param(),
FindImplementationCase( FindImplementationCase(
language=Language.JAVA, ls_id=LanguageServerId.JAVA,
id="java_greeter_format", id="java_greeter_format",
symbol_name="Greeter/formatGreeting", symbol_name="Greeter/formatGreeting",
definition_file=os.path.join("src", "main", "java", "test_repo", "Greeter.java"), definition_file=os.path.join("src", "main", "java", "test_repo", "Greeter.java"),
@@ -437,7 +436,7 @@ FIND_IMPLEMENTATION_CASES = [
expected_symbol_name="formatGreeting", expected_symbol_name="formatGreeting",
).to_pytest_param(), ).to_pytest_param(),
FindImplementationCase( FindImplementationCase(
language=Language.RUST, ls_id=LanguageServerId.RUST,
id="rust_greeter_format", id="rust_greeter_format",
symbol_name="Greeter/format_greeting", symbol_name="Greeter/format_greeting",
definition_file=os.path.join("src", "lib.rs"), definition_file=os.path.join("src", "lib.rs"),
@@ -445,7 +444,7 @@ FIND_IMPLEMENTATION_CASES = [
expected_symbol_name="format_greeting", expected_symbol_name="format_greeting",
).to_pytest_param(), ).to_pytest_param(),
FindImplementationCase( FindImplementationCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id="typescript_greeter_format", id="typescript_greeter_format",
symbol_name="Greeter/formatGreeting", symbol_name="Greeter/formatGreeting",
definition_file="formatters.ts", definition_file="formatters.ts",
@@ -457,153 +456,157 @@ FIND_IMPLEMENTATION_CASES = [
FIND_SYMBOL_REFERENCES_CASES = [ FIND_SYMBOL_REFERENCES_CASES = [
FindSymbolCase( FindSymbolCase(
language=Language.PYTHON, id="python_user_class", symbol_name="User", expected_kind="Class", expected_file="models.py" ls_id=LanguageServerId.PYTHON, id="python_user_class", symbol_name="User", expected_kind="Class", expected_file="models.py"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.GO, id="go_helper_function", symbol_name="Helper", expected_kind="Function", expected_file="main.go" ls_id=LanguageServerId.GO, id="go_helper_function", symbol_name="Helper", expected_kind="Function", expected_file="main.go"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.JAVA, id="java_model_class", symbol_name="Model", expected_kind="Class", expected_file="Model.java" ls_id=LanguageServerId.JAVA, id="java_model_class", symbol_name="Model", expected_kind="Class", expected_file="Model.java"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.KOTLIN, id="kotlin_model_struct", symbol_name="Model", expected_kind="Struct", expected_file="Model.kt" ls_id=LanguageServerId.KOTLIN, id="kotlin_model_struct", symbol_name="Model", expected_kind="Struct", expected_file="Model.kt"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id="typescript_demo_class", id="typescript_demo_class",
symbol_name="DemoClass", symbol_name="DemoClass",
expected_kind="Class", expected_kind="Class",
expected_file="index.ts", expected_file="index.ts",
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.PHP, ls_id=LanguageServerId.PHP,
id="php_helper_function", id="php_helper_function",
symbol_name="helperFunction", symbol_name="helperFunction",
expected_kind="Function", expected_kind="Function",
expected_file="helper.php", expected_file="helper.php",
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.CLOJURE, ls_id=LanguageServerId.CLOJURE,
id="clojure_greet_function", id="clojure_greet_function",
symbol_name="greet", symbol_name="greet",
expected_kind="Function", expected_kind="Function",
expected_file=os.path.join("src", "test_app", "core.clj"), expected_file=os.path.join("src", "test_app", "core.clj"),
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.CSHARP, ls_id=LanguageServerId.CSHARP,
id="csharp_calculator_class", id="csharp_calculator_class",
symbol_name="Calculator", symbol_name="Calculator",
expected_kind="Class", expected_kind="Class",
expected_file="Program.cs", expected_file="Program.cs",
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.POWERSHELL, ls_id=LanguageServerId.POWERSHELL,
id="powershell_greet_user", id="powershell_greet_user",
symbol_name="Greet-User", symbol_name="Greet-User",
expected_kind="Function", expected_kind="Function",
expected_file="main.ps1", expected_file="main.ps1",
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.CPP, id="cpp_add_function", symbol_name="add", expected_kind="Function", expected_file="b.cpp" ls_id=LanguageServerId.CPP, id="cpp_add_function", symbol_name="add", expected_kind="Function", expected_file="b.cpp"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.LEAN4, id="lean_add_method", symbol_name="add", expected_kind="Method", expected_file="Helper.lean" ls_id=LanguageServerId.LEAN4, id="lean_add_method", symbol_name="add", expected_kind="Method", expected_file="Helper.lean"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.FSHARP, ls_id=LanguageServerId.FSHARP,
id="fsharp_calculator_module", id="fsharp_calculator_module",
symbol_name="Calculator", symbol_name="Calculator",
expected_kind="Module", expected_kind="Module",
expected_file="Calculator.fs", expected_file="Calculator.fs",
).to_pytest_param(pytest.mark.xfail(reason="F# language server is unreliable")), ).to_pytest_param(pytest.mark.xfail(reason="F# language server is unreliable")),
FindSymbolCase( FindSymbolCase(
language=Language.RUST, id="rust_add_function", symbol_name="add", expected_kind="Function", expected_file="lib.rs" ls_id=LanguageServerId.RUST, id="rust_add_function", symbol_name="add", expected_kind="Function", expected_file="lib.rs"
).to_pytest_param(), ).to_pytest_param(),
FindSymbolCase( FindSymbolCase(
language=Language.LATEX, id="latex_methods_section", symbol_name="Methods", expected_kind="Module", expected_file="main.tex" ls_id=LanguageServerId.LATEX, id="latex_methods_section", symbol_name="Methods", expected_kind="Module", expected_file="main.tex"
).to_pytest_param(), ).to_pytest_param(),
] ]
FIND_REFERENCE_CASES = [ FIND_REFERENCE_CASES = [
FindReferenceCase( FindReferenceCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_user_refs", id="python_user_refs",
symbol_name="User", symbol_name="User",
definition_file=os.path.join("test_repo", "models.py"), definition_file=os.path.join("test_repo", "models.py"),
reference_file=os.path.join("test_repo", "services.py"), reference_file=os.path.join("test_repo", "services.py"),
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.GO, id="go_helper_refs", symbol_name="Helper", definition_file="main.go", reference_file="main.go" ls_id=LanguageServerId.GO, id="go_helper_refs", symbol_name="Helper", definition_file="main.go", reference_file="main.go"
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.JAVA, ls_id=LanguageServerId.JAVA,
id="java_model_refs", id="java_model_refs",
symbol_name="Model", symbol_name="Model",
definition_file=os.path.join("src", "main", "java", "test_repo", "Model.java"), definition_file=os.path.join("src", "main", "java", "test_repo", "Model.java"),
reference_file=os.path.join("src", "main", "java", "test_repo", "Main.java"), reference_file=os.path.join("src", "main", "java", "test_repo", "Main.java"),
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.KOTLIN, ls_id=LanguageServerId.KOTLIN,
id="kotlin_model_refs", id="kotlin_model_refs",
symbol_name="Model", symbol_name="Model",
definition_file=os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"), definition_file=os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
reference_file=os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"), reference_file=os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.RUST, ls_id=LanguageServerId.RUST,
id="rust_add_refs", id="rust_add_refs",
symbol_name="add", symbol_name="add",
definition_file=os.path.join("src", "lib.rs"), definition_file=os.path.join("src", "lib.rs"),
reference_file=os.path.join("src", "main.rs"), reference_file=os.path.join("src", "main.rs"),
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.PHP, ls_id=LanguageServerId.PHP,
id="php_helper_refs", id="php_helper_refs",
symbol_name="helperFunction", symbol_name="helperFunction",
definition_file="helper.php", definition_file="helper.php",
reference_file="index.php", reference_file="index.php",
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.CLOJURE, ls_id=LanguageServerId.CLOJURE,
id="clojure_multiply_refs", id="clojure_multiply_refs",
symbol_name="multiply", symbol_name="multiply",
definition_file=os.path.join("src", "test_app", "core.clj"), definition_file=os.path.join("src", "test_app", "core.clj"),
reference_file=os.path.join("src", "test_app", "utils.clj"), reference_file=os.path.join("src", "test_app", "utils.clj"),
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.CSHARP, ls_id=LanguageServerId.CSHARP,
id="csharp_calculator_refs", id="csharp_calculator_refs",
symbol_name="Calculator", symbol_name="Calculator",
definition_file="Program.cs", definition_file="Program.cs",
reference_file="Program.cs", reference_file="Program.cs",
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.POWERSHELL, ls_id=LanguageServerId.POWERSHELL,
id="powershell_greet_user_refs", id="powershell_greet_user_refs",
symbol_name="Greet-User", symbol_name="Greet-User",
definition_file="main.ps1", definition_file="main.ps1",
reference_file="main.ps1", reference_file="main.ps1",
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.CPP, id="cpp_add_refs", symbol_name="add", definition_file="b.cpp", reference_file="a.cpp" ls_id=LanguageServerId.CPP, id="cpp_add_refs", symbol_name="add", definition_file="b.cpp", reference_file="a.cpp"
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.LEAN4, id="lean_add_refs", symbol_name="add", definition_file="Helper.lean", reference_file="Main.lean" ls_id=LanguageServerId.LEAN4, id="lean_add_refs", symbol_name="add", definition_file="Helper.lean", reference_file="Main.lean"
).to_pytest_param(), ).to_pytest_param(),
FindReferenceCase( FindReferenceCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id="typescript_helper_refs", id="typescript_helper_refs",
symbol_name="helperFunction", symbol_name="helperFunction",
definition_file="index.ts", definition_file="index.ts",
reference_file="use_helper.ts", reference_file="use_helper.ts",
).to_pytest_param(pytest.mark.xfail(False, reason="TypeScript language server is unreliable")), ).to_pytest_param(pytest.mark.xfail(False, reason="TypeScript language server is unreliable")),
FindReferenceCase( FindReferenceCase(
language=Language.FSHARP, id="fsharp_add_refs", symbol_name="add", definition_file="Calculator.fs", reference_file="Program.fs" ls_id=LanguageServerId.FSHARP,
id="fsharp_add_refs",
symbol_name="add",
definition_file="Calculator.fs",
reference_file="Program.fs",
).to_pytest_param( ).to_pytest_param(
pytest.mark.xfail(reason="F# language server is unreliable"), # See issue #1040 pytest.mark.xfail(reason="F# language server is unreliable"), # See issue #1040
), ),
FindReferenceCase( FindReferenceCase(
language=Language.LATEX, ls_id=LanguageServerId.LATEX,
id="latex_background_refs", id="latex_background_refs",
symbol_name="Background", symbol_name="Background",
definition_file="sections/background.tex", definition_file="sections/background.tex",
@@ -613,7 +616,7 @@ FIND_REFERENCE_CASES = [
FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [ FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [
RegexDefiningSymbolErrorCase( RegexDefiningSymbolErrorCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_regex_multiple_matches", id="python_regex_multiple_matches",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
regex=r"(User)", regex=r"(User)",
@@ -621,7 +624,7 @@ FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [
error_fragment="Match must be unique", error_fragment="Match must be unique",
).to_pytest_param(), ).to_pytest_param(),
RegexDefiningSymbolErrorCase( RegexDefiningSymbolErrorCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_regex_missing_group", id="python_regex_missing_group",
relative_path=os.path.join("test_repo", "services.py"), relative_path=os.path.join("test_repo", "services.py"),
regex=r"self.users.get\(id\)", regex=r"self.users.get\(id\)",
@@ -632,7 +635,7 @@ FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [
FIND_SYMBOL_NAME_PATH_CASES = [ FIND_SYMBOL_NAME_PATH_CASES = [
FindSymbolNamePathCase( FindSymbolNamePathCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="nested_class_exact", id="nested_class_exact",
name_path="OuterClass/NestedClass", name_path="OuterClass/NestedClass",
substring_matching=False, substring_matching=False,
@@ -641,7 +644,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
expected_file=os.path.join("test_repo", "nested.py"), expected_file=os.path.join("test_repo", "nested.py"),
).to_pytest_param(), ).to_pytest_param(),
FindSymbolNamePathCase( FindSymbolNamePathCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="nested_method_exact", id="nested_method_exact",
name_path="OuterClass/NestedClass/find_me", name_path="OuterClass/NestedClass/find_me",
substring_matching=False, substring_matching=False,
@@ -650,7 +653,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
expected_file=os.path.join("test_repo", "nested.py"), expected_file=os.path.join("test_repo", "nested.py"),
).to_pytest_param(), ).to_pytest_param(),
FindSymbolNamePathCase( FindSymbolNamePathCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="nested_class_substring", id="nested_class_substring",
name_path="OuterClass/NestedCl", name_path="OuterClass/NestedCl",
substring_matching=True, substring_matching=True,
@@ -659,7 +662,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
expected_file=os.path.join("test_repo", "nested.py"), expected_file=os.path.join("test_repo", "nested.py"),
).to_pytest_param(), ).to_pytest_param(),
FindSymbolNamePathCase( FindSymbolNamePathCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="nested_method_substring", id="nested_method_substring",
name_path="OuterClass/NestedClass/find_m", name_path="OuterClass/NestedClass/find_m",
substring_matching=True, substring_matching=True,
@@ -668,7 +671,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
expected_file=os.path.join("test_repo", "nested.py"), expected_file=os.path.join("test_repo", "nested.py"),
).to_pytest_param(), ).to_pytest_param(),
FindSymbolNamePathCase( FindSymbolNamePathCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="outer_class_absolute", id="outer_class_absolute",
name_path="/OuterClass", name_path="/OuterClass",
substring_matching=False, substring_matching=False,
@@ -677,7 +680,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
expected_file=os.path.join("test_repo", "nested.py"), expected_file=os.path.join("test_repo", "nested.py"),
).to_pytest_param(), ).to_pytest_param(),
FindSymbolNamePathCase( FindSymbolNamePathCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="nested_method_absolute_substring", id="nested_method_absolute_substring",
name_path="/OuterClass/NestedClass/find_m", name_path="/OuterClass/NestedClass/find_m",
substring_matching=True, substring_matching=True,
@@ -688,21 +691,21 @@ FIND_SYMBOL_NAME_PATH_CASES = [
] ]
FIND_SYMBOL_NAME_PATH_NO_MATCH_CASES = [ FIND_SYMBOL_NAME_PATH_NO_MATCH_CASES = [
FindSymbolNoMatchCase(language=Language.PYTHON, id="nested_class_not_top_level", name_path="/NestedClass").to_pytest_param(), FindSymbolNoMatchCase(ls_id=LanguageServerId.PYTHON, id="nested_class_not_top_level", name_path="/NestedClass").to_pytest_param(),
FindSymbolNoMatchCase( FindSymbolNoMatchCase(
language=Language.PYTHON, id="nested_class_missing_parent", name_path="/NoSuchParent/NestedClass" ls_id=LanguageServerId.PYTHON, id="nested_class_missing_parent", name_path="/NoSuchParent/NestedClass"
).to_pytest_param(), ).to_pytest_param(),
] ]
FIND_SYMBOL_OVERLOADED_FUNCTION_CASES = [ FIND_SYMBOL_OVERLOADED_FUNCTION_CASES = [
FindSymbolOverloadedCase( FindSymbolOverloadedCase(
language=Language.JAVA, id="java_overloaded_get_name", name_path="Model/getName", num_expected=2 ls_id=LanguageServerId.JAVA, id="java_overloaded_get_name", name_path="Model/getName", num_expected=2
).to_pytest_param(), ).to_pytest_param(),
] ]
NON_UNIQUE_SYMBOL_REFERENCE_ERROR_CASES = [ NON_UNIQUE_SYMBOL_REFERENCE_ERROR_CASES = [
NonUniqueSymbolReferenceCase( NonUniqueSymbolReferenceCase(
language=Language.JAVA, ls_id=LanguageServerId.JAVA,
id="java_overloaded_get_name", id="java_overloaded_get_name",
name_path="Model/getName", name_path="Model/getName",
relative_path=os.path.join("src", "main", "java", "test_repo", "Model.java"), relative_path=os.path.join("src", "main", "java", "test_repo", "Model.java"),
@@ -711,25 +714,25 @@ NON_UNIQUE_SYMBOL_REFERENCE_ERROR_CASES = [
SAFE_DELETE_BLOCKED_CASES = [ SAFE_DELETE_BLOCKED_CASES = [
SafeDeleteCase( SafeDeleteCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_user", id="python_user",
name_path="User", name_path="User",
relative_path=os.path.join("test_repo", "models.py"), relative_path=os.path.join("test_repo", "models.py"),
).to_pytest_param(), ).to_pytest_param(),
SafeDeleteCase( SafeDeleteCase(
language=Language.JAVA, ls_id=LanguageServerId.JAVA,
id="java_model", id="java_model",
name_path="Model", name_path="Model",
relative_path=os.path.join("src", "main", "java", "test_repo", "Model.java"), relative_path=os.path.join("src", "main", "java", "test_repo", "Model.java"),
).to_pytest_param(), ).to_pytest_param(),
SafeDeleteCase( SafeDeleteCase(
language=Language.KOTLIN, ls_id=LanguageServerId.KOTLIN,
id="kotlin_model", id="kotlin_model",
name_path="Model", name_path="Model",
relative_path=os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"), relative_path=os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
).to_pytest_param(), ).to_pytest_param(),
SafeDeleteCase( SafeDeleteCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id="typescript_helper_function", id="typescript_helper_function",
name_path="helperFunction", name_path="helperFunction",
relative_path="index.ts", relative_path="index.ts",
@@ -738,25 +741,25 @@ SAFE_DELETE_BLOCKED_CASES = [
SAFE_DELETE_SUCCEEDS_CASES = [ SAFE_DELETE_SUCCEEDS_CASES = [
SafeDeleteCase( SafeDeleteCase(
language=Language.PYTHON, ls_id=LanguageServerId.PYTHON,
id="python_timer", id="python_timer",
name_path="Timer", name_path="Timer",
relative_path=os.path.join("test_repo", "utils.py"), relative_path=os.path.join("test_repo", "utils.py"),
).to_pytest_param(), ).to_pytest_param(),
SafeDeleteCase( SafeDeleteCase(
language=Language.JAVA, ls_id=LanguageServerId.JAVA,
id="java_model_user", id="java_model_user",
name_path="ModelUser", name_path="ModelUser",
relative_path=os.path.join("src", "main", "java", "test_repo", "ModelUser.java"), relative_path=os.path.join("src", "main", "java", "test_repo", "ModelUser.java"),
).to_pytest_param(), ).to_pytest_param(),
SafeDeleteCase( SafeDeleteCase(
language=Language.KOTLIN, ls_id=LanguageServerId.KOTLIN,
id="kotlin_model_user", id="kotlin_model_user",
name_path="ModelUser", name_path="ModelUser",
relative_path=os.path.join("src", "main", "kotlin", "test_repo", "ModelUser.kt"), relative_path=os.path.join("src", "main", "kotlin", "test_repo", "ModelUser.kt"),
).to_pytest_param(), ).to_pytest_param(),
SafeDeleteCase( SafeDeleteCase(
language=Language.TYPESCRIPT, ls_id=LanguageServerId.TYPESCRIPT,
id="typescript_unused_standalone_function", id="typescript_unused_standalone_function",
name_path="unusedStandaloneFunction", name_path="unusedStandaloneFunction",
relative_path="index.ts", relative_path="index.ts",
@@ -771,23 +774,23 @@ def serena_config():
# Create test projects for all supported languages # Create test projects for all supported languages
test_projects = [] test_projects = []
for language in [ for language in [
Language.PYTHON, LanguageServerId.PYTHON,
Language.PYTHON_TY, LanguageServerId.PYTHON_TY,
Language.GO, LanguageServerId.GO,
Language.JAVA, LanguageServerId.JAVA,
Language.KOTLIN, LanguageServerId.KOTLIN,
Language.RUST, LanguageServerId.RUST,
Language.TYPESCRIPT, LanguageServerId.TYPESCRIPT,
Language.PHP, LanguageServerId.PHP,
Language.CSHARP, LanguageServerId.CSHARP,
Language.CLOJURE, LanguageServerId.CLOJURE,
Language.FSHARP, LanguageServerId.FSHARP,
Language.POWERSHELL, LanguageServerId.POWERSHELL,
Language.CPP, LanguageServerId.CPP,
Language.HAXE, LanguageServerId.HAXE,
Language.LEAN4, LanguageServerId.LEAN4,
Language.MSL, LanguageServerId.MSL,
Language.LATEX, LanguageServerId.LATEX,
]: ]:
repo_path = get_repo_path(language) repo_path = get_repo_path(language)
if repo_path.exists(): if repo_path.exists():
@@ -845,8 +848,8 @@ def project_file_modification_context(serena_agent: SerenaAgent, relative_path:
@pytest.fixture @pytest.fixture
def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[SerenaAgent]: def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[SerenaAgent]:
language = Language(request.param) language = LanguageServerId(request.param)
if not language_tests_enabled(language): if not language_server_tests_enabled(language):
pytest.skip(f"Tests for language {language} are not enabled.") pytest.skip(f"Tests for language {language} are not enabled.")
project_name = f"test_repo_{language}" project_name = f"test_repo_{language}"
@@ -865,7 +868,7 @@ def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[Sere
class TestSerenaAgent: class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"project", "project",
[None, str(get_repo_path(Language.PYTHON)), "non_existent_path"], [None, str(get_repo_path(LanguageServerId.PYTHON)), "non_existent_path"],
ids=["no_project", "python_project_path", "invalid_project_path"], ids=["no_project", "python_project_path", "invalid_project_path"],
) )
def test_agent_instantiation(self, project: str | None): def test_agent_instantiation(self, project: str | None):
@@ -880,7 +883,7 @@ class TestSerenaAgent:
SerenaAgent(project=project, serena_config=serena_config) SerenaAgent(project=project, serena_config=serena_config)
@pytest.mark.python @pytest.mark.python
@pytest.mark.skipif(not language_tests_enabled(Language.PYTHON), reason="python tests are disabled in this environment") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.PYTHON), reason="python tests are disabled in this environment")
def test_grok_context_restricts_toolset_and_prompt(self, serena_config): def test_grok_context_restricts_toolset_and_prompt(self, serena_config):
agent = SerenaAgent( agent = SerenaAgent(
project="test_repo_python", project="test_repo_python",
@@ -911,7 +914,7 @@ class TestSerenaAgent:
symbol: dict, symbol: dict,
expected_name: str | None = None, expected_name: str | None = None,
) -> None: ) -> None:
if serena_agent.get_active_lsp_languages() == [Language.KOTLIN]: if serena_agent.get_active_language_server_ids() == [LanguageServerId.KOTLIN]:
# kotlin LS doesn't seem to provide hover info right now, at least for the struct we test this on # kotlin LS doesn't seem to provide hover info right now, at least for the struct we test this on
return return
@@ -924,12 +927,12 @@ class TestSerenaAgent:
if expected_name is not None: if expected_name is not None:
assert expected_name in symbol_info, ( assert expected_name in symbol_info, (
f"[{serena_agent.get_active_lsp_languages()[0]}] Expected symbol info to contain symbol name " f"[{serena_agent.get_active_language_server_ids()[0]}] Expected symbol info to contain symbol name "
f"{expected_name}. Info: {symbol_info}" f"{expected_name}. Info: {symbol_info}"
) )
# special additional test for Java, since Eclipse returns hover in a complex format and we want to make sure to get it right # special additional test for Java, since Eclipse returns hover in a complex format and we want to make sure to get it right
if symbol["kind"] == SymbolKind.Class.name and serena_agent.get_active_lsp_languages() == [Language.JAVA]: if symbol["kind"] == SymbolKind.Class.name and serena_agent.get_active_language_server_ids() == [LanguageServerId.JAVA]:
assert "A simple model class" in symbol_info, f"Java class docstring not found in symbol info: {symbol}" assert "A simple model class" in symbol_info, f"Java class docstring not found in symbol info: {symbol}"
@pytest.mark.parametrize("serena_agent,case", FIND_SYMBOL_REFERENCES_CASES, indirect=["serena_agent"]) @pytest.mark.parametrize("serena_agent,case", FIND_SYMBOL_REFERENCES_CASES, indirect=["serena_agent"])
@@ -1030,7 +1033,7 @@ class TestSerenaAgent:
diagnostic_case.assert_matches(full_file_diagnostics) diagnostic_case.assert_matches(full_file_diagnostics)
# testing diagnostics in range by removing second symbol # testing diagnostics in range by removing second symbol
project_root = get_repo_path(diagnostic_case.language) project_root = get_repo_path(diagnostic_case.ls_id)
pos1 = find_identifier_pos(project_root / diagnostic_case.relative_path, diagnostic_case.symbol1_id_str) pos1 = find_identifier_pos(project_root / diagnostic_case.relative_path, diagnostic_case.symbol1_id_str)
pos2 = find_identifier_pos(project_root / diagnostic_case.relative_path, cast(str, diagnostic_case.symbol2_id_str)) pos2 = find_identifier_pos(project_root / diagnostic_case.relative_path, cast(str, diagnostic_case.symbol2_id_str))
assert pos1 is not None assert pos1 is not None
@@ -1141,7 +1144,7 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.TYPESCRIPT, marks=get_pytest_markers(Language.TYPESCRIPT), id="typescript_unique_regex"), pytest.param(LanguageServerId.TYPESCRIPT, marks=get_pytest_markers(LanguageServerId.TYPESCRIPT), id="typescript_unique_regex"),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1163,7 +1166,7 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.TYPESCRIPT, marks=get_pytest_markers(Language.TYPESCRIPT), id="typescript_backslashes"), pytest.param(LanguageServerId.TYPESCRIPT, marks=get_pytest_markers(LanguageServerId.TYPESCRIPT), id="typescript_backslashes"),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1191,7 +1194,7 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.PYTHON, marks=get_pytest_markers(Language.PYTHON), id="python_replace_in_files"), pytest.param(LanguageServerId.PYTHON, marks=get_pytest_markers(LanguageServerId.PYTHON), id="python_replace_in_files"),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1221,7 +1224,7 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.PYTHON, marks=get_pytest_markers(Language.PYTHON), id="python_replace_in_files_guard"), pytest.param(LanguageServerId.PYTHON, marks=get_pytest_markers(LanguageServerId.PYTHON), id="python_replace_in_files_guard"),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1242,8 +1245,8 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.PYTHON, marks=get_pytest_markers(Language.PYTHON), id="python_services"), pytest.param(LanguageServerId.PYTHON, marks=get_pytest_markers(LanguageServerId.PYTHON), id="python_services"),
pytest.param(Language.PYTHON_TY, marks=get_pytest_markers(Language.PYTHON_TY), id="python_ty_services"), pytest.param(LanguageServerId.PYTHON_TY, marks=get_pytest_markers(LanguageServerId.PYTHON_TY), id="python_ty_services"),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1273,8 +1276,8 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.PYTHON, marks=get_pytest_markers(Language.PYTHON), id="python_container_body"), pytest.param(LanguageServerId.PYTHON, marks=get_pytest_markers(LanguageServerId.PYTHON), id="python_container_body"),
pytest.param(Language.PYTHON_TY, marks=get_pytest_markers(Language.PYTHON_TY), id="python_ty_container_body"), pytest.param(LanguageServerId.PYTHON_TY, marks=get_pytest_markers(LanguageServerId.PYTHON_TY), id="python_ty_container_body"),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1306,7 +1309,9 @@ class TestSerenaAgent:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"serena_agent", "serena_agent",
[ [
pytest.param(Language.TYPESCRIPT, marks=get_pytest_markers(Language.TYPESCRIPT), id="typescript_ambiguous_regex"), pytest.param(
LanguageServerId.TYPESCRIPT, marks=get_pytest_markers(LanguageServerId.TYPESCRIPT), id="typescript_ambiguous_regex"
),
], ],
indirect=["serena_agent"], indirect=["serena_agent"],
) )
@@ -1375,7 +1380,7 @@ class TestPromptProvision:
else: else:
assert match is None, f"Expected no project activation message in result:\n{result}" assert match is None, f"Expected no project activation message in result:\n{result}"
@pytest.mark.parametrize("serena_agent", [Language.PYTHON], indirect=True) @pytest.mark.parametrize("serena_agent", [LanguageServerId.PYTHON], indirect=True)
def test_initial_instructions_provide_project_activation_message_once_per_session(self, serena_agent: SerenaAgent) -> None: def test_initial_instructions_provide_project_activation_message_once_per_session(self, serena_agent: SerenaAgent) -> None:
""" """
Tests that the project activation message is provided on the first call to InitialInstructionsTool for a session, Tests that the project activation message is provided on the first call to InitialInstructionsTool for a session,
@@ -1394,7 +1399,7 @@ class TestPromptProvision:
result3 = self._call_tool(serena_agent, InitialInstructionsTool, session_id=session1) result3 = self._call_tool(serena_agent, InitialInstructionsTool, session_id=session1)
self._assert_activation_message(result3, project_name, present=False) self._assert_activation_message(result3, project_name, present=False)
@pytest.mark.parametrize("serena_agent", [Language.PYTHON], indirect=True) @pytest.mark.parametrize("serena_agent", [LanguageServerId.PYTHON], indirect=True)
def test_dynamically_activated_mode_is_provided_once_per_session(self, serena_agent: SerenaAgent) -> None: def test_dynamically_activated_mode_is_provided_once_per_session(self, serena_agent: SerenaAgent) -> None:
""" """
Tests that when a new project is activated within a session that has a different mode configuration (e.g. no-onboarding), Tests that when a new project is activated within a session that has a different mode configuration (e.g. no-onboarding),
@@ -1437,7 +1442,7 @@ class TestPromptProvision:
# the initial instructions for the new session must also include the activation message for the project # the initial instructions for the new session must also include the activation message for the project
self._assert_activation_message(result4, project_name2, present=True) self._assert_activation_message(result4, project_name2, present=True)
@pytest.mark.parametrize("serena_agent", [Language.PYTHON], indirect=True) @pytest.mark.parametrize("serena_agent", [LanguageServerId.PYTHON], indirect=True)
def test_activate_project_tool_always_returns_activation_message(self, serena_agent: SerenaAgent) -> None: def test_activate_project_tool_always_returns_activation_message(self, serena_agent: SerenaAgent) -> None:
project_name = "test_repo_python" project_name = "test_repo_python"
session = "session1" session = "session1"
+42 -42
View File
@@ -22,9 +22,9 @@ from overrides import overrides
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from serena.code_editor import CodeEditor, LanguageServerCodeEditor from serena.code_editor import CodeEditor, LanguageServerCodeEditor
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from src.serena.symbol import LanguageServerSymbolRetriever from src.serena.symbol import LanguageServerSymbolRetriever
from test.conftest import get_repo_path, language_tests_enabled, project_with_ls_context from test.conftest import get_repo_path, language_server_tests_enabled, project_with_ls_context
pytestmark = pytest.mark.snapshot pytestmark = pytest.mark.snapshot
@@ -177,14 +177,14 @@ class CodeDiff:
class EditingTest(ABC): class EditingTest(ABC):
def __init__(self, language: Language, rel_path: str): def __init__(self, ls_id: LanguageServerId, rel_path: str):
""" """
:param language: the language :param ls_id: the language server to use
:param rel_path: the relative path of the edited file :param rel_path: the relative path of the edited file
""" """
self.rel_path = rel_path self.rel_path = rel_path
self.language = language self.ls_id = ls_id
self.original_repo_path = get_repo_path(language) self.original_repo_path = get_repo_path(ls_id)
self.repo_path: Path | None = None self.repo_path: Path | None = None
@contextmanager @contextmanager
@@ -199,8 +199,8 @@ class EditingTest(ABC):
# wait for a long time here # wait for a long time here
if os.name == "nt": if os.name == "nt":
time.sleep(0.1) time.sleep(0.1)
log.info(f"Creating language server for {self.language} {self.rel_path}") log.info(f"Creating language server for {self.ls_id} {self.rel_path}")
with project_with_ls_context(self.language, str(self.repo_path)) as project: with project_with_ls_context(self.ls_id, str(self.repo_path)) as project:
yield LanguageServerSymbolRetriever(project) yield LanguageServerSymbolRetriever(project)
finally: finally:
# prevent deadlock on Windows due to lingering file locks # prevent deadlock on Windows due to lingering file locks
@@ -244,8 +244,8 @@ TYPESCRIPT_TEST_FILE = "index.ts"
class DeleteSymbolTest(EditingTest): class DeleteSymbolTest(EditingTest):
def __init__(self, language: Language, rel_path: str, deleted_symbol: str): def __init__(self, ls_id: LanguageServerId, rel_path: str, deleted_symbol: str):
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.deleted_symbol = deleted_symbol self.deleted_symbol = deleted_symbol
self.rel_path = rel_path self.rel_path = rel_path
@@ -258,7 +258,7 @@ class DeleteSymbolTest(EditingTest):
[ [
pytest.param( pytest.param(
DeleteSymbolTest( DeleteSymbolTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"VariableContainer", "VariableContainer",
), ),
@@ -266,7 +266,7 @@ class DeleteSymbolTest(EditingTest):
), ),
pytest.param( pytest.param(
DeleteSymbolTest( DeleteSymbolTest(
Language.TYPESCRIPT, LanguageServerId.TYPESCRIPT,
TYPESCRIPT_TEST_FILE, TYPESCRIPT_TEST_FILE,
"DemoClass", "DemoClass",
), ),
@@ -307,9 +307,9 @@ NEW_TYPESCRIPT_FUNCTION_AFTER = """function newFunctionAfterClass(): void {
class InsertInRelToSymbolTest(EditingTest): class InsertInRelToSymbolTest(EditingTest):
def __init__( def __init__(
self, language: Language, rel_path: str, symbol_name: str, new_content: str, mode: Literal["before", "after"] | None = None self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_content: str, mode: Literal["before", "after"] | None = None
): ):
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.symbol_name = symbol_name self.symbol_name = symbol_name
self.new_content = new_content self.new_content = new_content
self.mode: Literal["before", "after"] | None = mode self.mode: Literal["before", "after"] | None = mode
@@ -331,7 +331,7 @@ class InsertInRelToSymbolTest(EditingTest):
[ [
pytest.param( pytest.param(
InsertInRelToSymbolTest( InsertInRelToSymbolTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"use_module_variables", "use_module_variables",
NEW_PYTHON_FUNCTION, NEW_PYTHON_FUNCTION,
@@ -340,7 +340,7 @@ class InsertInRelToSymbolTest(EditingTest):
), ),
pytest.param( pytest.param(
InsertInRelToSymbolTest( InsertInRelToSymbolTest(
Language.TYPESCRIPT, LanguageServerId.TYPESCRIPT,
TYPESCRIPT_TEST_FILE, TYPESCRIPT_TEST_FILE,
"DemoClass", "DemoClass",
NEW_TYPESCRIPT_FUNCTION_AFTER, NEW_TYPESCRIPT_FUNCTION_AFTER,
@@ -349,7 +349,7 @@ class InsertInRelToSymbolTest(EditingTest):
), ),
pytest.param( pytest.param(
InsertInRelToSymbolTest( InsertInRelToSymbolTest(
Language.TYPESCRIPT, LanguageServerId.TYPESCRIPT,
TYPESCRIPT_TEST_FILE, TYPESCRIPT_TEST_FILE,
"helperFunction", "helperFunction",
NEW_TYPESCRIPT_FUNCTION, NEW_TYPESCRIPT_FUNCTION,
@@ -366,7 +366,7 @@ def test_insert_in_rel_to_symbol(test_case: InsertInRelToSymbolTest, mode: Liter
@pytest.mark.python @pytest.mark.python
def test_insert_python_class_before(snapshot: SnapshotAssertion): def test_insert_python_class_before(snapshot: SnapshotAssertion):
InsertInRelToSymbolTest( InsertInRelToSymbolTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"VariableDataclass", "VariableDataclass",
NEW_PYTHON_CLASS_WITH_TRAILING_NEWLINES, NEW_PYTHON_CLASS_WITH_TRAILING_NEWLINES,
@@ -377,7 +377,7 @@ def test_insert_python_class_before(snapshot: SnapshotAssertion):
@pytest.mark.python @pytest.mark.python
def test_insert_python_class_after(snapshot: SnapshotAssertion): def test_insert_python_class_after(snapshot: SnapshotAssertion):
InsertInRelToSymbolTest( InsertInRelToSymbolTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"VariableDataclass", "VariableDataclass",
NEW_PYTHON_CLASS_WITH_LEADING_NEWLINES, NEW_PYTHON_CLASS_WITH_LEADING_NEWLINES,
@@ -399,8 +399,8 @@ TYPESCRIPT_REPLACED_BODY = """function printValue() {
class ReplaceBodyTest(EditingTest): class ReplaceBodyTest(EditingTest):
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_body: str): def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_body: str):
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.symbol_name = symbol_name self.symbol_name = symbol_name
self.new_body = new_body self.new_body = new_body
@@ -413,7 +413,7 @@ class ReplaceBodyTest(EditingTest):
[ [
pytest.param( pytest.param(
ReplaceBodyTest( ReplaceBodyTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"VariableContainer/modify_instance_var", "VariableContainer/modify_instance_var",
PYTHON_REPLACED_BODY, PYTHON_REPLACED_BODY,
@@ -422,7 +422,7 @@ class ReplaceBodyTest(EditingTest):
), ),
pytest.param( pytest.param(
ReplaceBodyTest( ReplaceBodyTest(
Language.TYPESCRIPT, LanguageServerId.TYPESCRIPT,
TYPESCRIPT_TEST_FILE, TYPESCRIPT_TEST_FILE,
"DemoClass/printValue", "DemoClass/printValue",
TYPESCRIPT_REPLACED_BODY, TYPESCRIPT_REPLACED_BODY,
@@ -442,8 +442,8 @@ NIX_ATTR_REPLACEMENT = """c = 3;"""
class NixAttrReplacementTest(EditingTest): class NixAttrReplacementTest(EditingTest):
"""Test for replacing individual attributes in Nix that should NOT result in double semicolons.""" """Test for replacing individual attributes in Nix that should NOT result in double semicolons."""
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_body: str): def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_body: str):
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.symbol_name = symbol_name self.symbol_name = symbol_name
self.new_body = new_body self.new_body = new_body
@@ -452,7 +452,7 @@ class NixAttrReplacementTest(EditingTest):
@pytest.mark.nix @pytest.mark.nix
@pytest.mark.skipif(not language_tests_enabled(Language.NIX), reason="Nix tests are disabled (nixd not available)") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.NIX), reason="Nix tests are disabled (nixd not available)")
def test_nix_symbol_replacement_no_double_semicolon(snapshot: SnapshotAssertion): def test_nix_symbol_replacement_no_double_semicolon(snapshot: SnapshotAssertion):
""" """
Test that replacing a Nix attribute does not result in double semicolons. Test that replacing a Nix attribute does not result in double semicolons.
@@ -467,7 +467,7 @@ def test_nix_symbol_replacement_no_double_semicolon(snapshot: SnapshotAssertion)
logic should prevent double semicolons. logic should prevent double semicolons.
""" """
test_case = NixAttrReplacementTest( test_case = NixAttrReplacementTest(
Language.NIX, LanguageServerId.NIX,
"default.nix", "default.nix",
"testUser", # Simple attrset with multiple key-value pairs "testUser", # Simple attrset with multiple key-value pairs
NIX_ATTR_REPLACEMENT, NIX_ATTR_REPLACEMENT,
@@ -486,8 +486,8 @@ class GoDeclReplacementTest(EditingTest):
in a duplicated leading keyword (e.g. ``type type NamedInt``). in a duplicated leading keyword (e.g. ``type type NamedInt``).
""" """
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_body: str): def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_body: str):
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.symbol_name = symbol_name self.symbol_name = symbol_name
self.new_body = new_body self.new_body = new_body
@@ -523,7 +523,7 @@ def test_go_symbol_replacement_no_double_keyword(snapshot: SnapshotAssertion):
range extension prevents the duplicated keyword. range extension prevents the duplicated keyword.
""" """
test_case = GoDeclReplacementTest( test_case = GoDeclReplacementTest(
Language.GO, LanguageServerId.GO,
"symbol_body.go", "symbol_body.go",
"NamedInt", "NamedInt",
GO_DECL_REPLACEMENT, GO_DECL_REPLACEMENT,
@@ -532,8 +532,8 @@ def test_go_symbol_replacement_no_double_keyword(snapshot: SnapshotAssertion):
class RenameSymbolTest(EditingTest): class RenameSymbolTest(EditingTest):
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_name: str): def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_name: str):
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.symbol_name = symbol_name self.symbol_name = symbol_name
self.new_name = new_name self.new_name = new_name
@@ -550,7 +550,7 @@ class RenameSymbolTest(EditingTest):
@pytest.mark.python @pytest.mark.python
def test_rename_symbol(snapshot: SnapshotAssertion): def test_rename_symbol(snapshot: SnapshotAssertion):
test_case = RenameSymbolTest( test_case = RenameSymbolTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"typed_module_var", "typed_module_var",
"renamed_typed_module_var", "renamed_typed_module_var",
@@ -564,15 +564,15 @@ class InsertAndDeleteSymbolTest(EditingTest):
in no change to the file content. in no change to the file content.
""" """
def __init__(self, language: Language, rel_path: str, rel_symbol: str, deleted_symbol: str, insertion: str): def __init__(self, ls_id: LanguageServerId, rel_path: str, rel_symbol: str, deleted_symbol: str, insertion: str):
""" """
:param language: specifies the language server to use :param ls_id: specifies the language server to use
:param rel_path: relative path of the file :param rel_path: relative path of the file
:param rel_symbol: symbol after which the insertion is made :param rel_symbol: symbol after which the insertion is made
:param deleted_symbol: the symbol to be deleted after the insertion :param deleted_symbol: the symbol to be deleted after the insertion
:param insertion: text which inserts the symbol `deleted_symbol` :param insertion: text which inserts the symbol `deleted_symbol`
""" """
super().__init__(language, rel_path) super().__init__(ls_id, rel_path)
self.rel_symbol = rel_symbol self.rel_symbol = rel_symbol
self.deleted_symbol = deleted_symbol self.deleted_symbol = deleted_symbol
self.rel_path = rel_path self.rel_path = rel_path
@@ -592,7 +592,7 @@ def test_insert_and_delete_no_change():
""" """
insertion = " def inserted_function():\n pass" insertion = " def inserted_function():\n pass"
test_case = InsertAndDeleteSymbolTest( test_case = InsertAndDeleteSymbolTest(
Language.PYTHON, LanguageServerId.PYTHON,
PYTHON_TEST_REL_FILE_PATH, PYTHON_TEST_REL_FILE_PATH,
"VariableContainer/modify_instance_var", "VariableContainer/modify_instance_var",
"VariableContainer/inserted_function", "VariableContainer/inserted_function",
@@ -617,7 +617,7 @@ NEW_VUE_HANDLER = """const handleDoubleClick = () => {
[ [
pytest.param( pytest.param(
DeleteSymbolTest( DeleteSymbolTest(
Language.VUE, LanguageServerId.VUE,
VUE_TEST_FILE, VUE_TEST_FILE,
"handleMouseEnter", "handleMouseEnter",
), ),
@@ -635,7 +635,7 @@ def test_delete_symbol_vue(test_case: DeleteSymbolTest, snapshot: SnapshotAssert
[ [
pytest.param( pytest.param(
InsertInRelToSymbolTest( InsertInRelToSymbolTest(
Language.VUE, LanguageServerId.VUE,
VUE_TEST_FILE, VUE_TEST_FILE,
"handleClick", "handleClick",
NEW_VUE_HANDLER, NEW_VUE_HANDLER,
@@ -666,7 +666,7 @@ VUE_REPLACED_HANDLECLICK_BODY = """const handleClick = () => {
[ [
pytest.param( pytest.param(
ReplaceBodyTest( ReplaceBodyTest(
Language.VUE, LanguageServerId.VUE,
VUE_TEST_FILE, VUE_TEST_FILE,
"handleClick", "handleClick",
VUE_REPLACED_HANDLECLICK_BODY, VUE_REPLACED_HANDLECLICK_BODY,
@@ -687,7 +687,7 @@ VUE_REPLACED_PRESSCOUNT_BODY = """const pressCount = ref(100)"""
[ [
pytest.param( pytest.param(
ReplaceBodyTest( ReplaceBodyTest(
Language.VUE, LanguageServerId.VUE,
VUE_TEST_FILE, VUE_TEST_FILE,
"pressCount", "pressCount",
VUE_REPLACED_PRESSCOUNT_BODY, VUE_REPLACED_PRESSCOUNT_BODY,
@@ -726,7 +726,7 @@ VUE_STORE_REPLACED_CLEAR_BODY = """function clear() {
[ [
pytest.param( pytest.param(
ReplaceBodyTest( ReplaceBodyTest(
Language.VUE, LanguageServerId.VUE,
VUE_STORE_FILE, VUE_STORE_FILE,
"clear", "clear",
VUE_STORE_REPLACED_CLEAR_BODY, VUE_STORE_REPLACED_CLEAR_BODY,
+12 -14
View File
@@ -1,9 +1,7 @@
"""Unit tests for :func:`serena.util.inspection.determine_programming_language_composition`."""
from pathlib import Path from pathlib import Path
from serena.util.inspection import determine_programming_language_composition from serena.util.inspection import compute_language_server_support_composition
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
def _touch(directory: Path, *names: str) -> None: def _touch(directory: Path, *names: str) -> None:
@@ -11,11 +9,11 @@ def _touch(directory: Path, *names: str) -> None:
(directory / name).write_text("content", encoding="utf-8") (directory / name).write_text("content", encoding="utf-8")
class TestDetermineProgrammingLanguageComposition: class TestComputeLanguageServerSupportComposition:
def test_single_language_repo(self, tmp_path: Path) -> None: def test_single_language_repo(self, tmp_path: Path) -> None:
_touch(tmp_path, "a.py", "b.py", "c.py") _touch(tmp_path, "a.py", "b.py", "c.py")
composition = determine_programming_language_composition(str(tmp_path)) composition = compute_language_server_support_composition(str(tmp_path))
assert composition[Language.PYTHON] == 100.0 assert composition[LanguageServerId.PYTHON] == 100.0
def test_unrecognised_files_do_not_dilute_percentages(self, tmp_path: Path) -> None: def test_unrecognised_files_do_not_dilute_percentages(self, tmp_path: Path) -> None:
# 2 source files vs many files that belong to no supported language # 2 source files vs many files that belong to no supported language
@@ -23,23 +21,23 @@ class TestDetermineProgrammingLanguageComposition:
_touch(tmp_path, *[f"note_{i}.txt" for i in range(20)]) _touch(tmp_path, *[f"note_{i}.txt" for i in range(20)])
_touch(tmp_path, "logo.png", "LICENSE", "data.csv") _touch(tmp_path, "logo.png", "LICENSE", "data.csv")
composition = determine_programming_language_composition(str(tmp_path)) composition = compute_language_server_support_composition(str(tmp_path))
# previously: 2 / 25 files = 8% — now the denominator is recognised source files only # previously: 2 / 25 files = 8% — now the denominator is recognised source files only
assert composition[Language.PYTHON] == 100.0 assert composition[LanguageServerId.PYTHON] == 100.0
def test_mixed_language_percentages_relative_to_recognised_files(self, tmp_path: Path) -> None: def test_mixed_language_percentages_relative_to_recognised_files(self, tmp_path: Path) -> None:
_touch(tmp_path, "a.py", "b.py", "c.py", "d.go") _touch(tmp_path, "a.py", "b.py", "c.py", "d.go")
_touch(tmp_path, *[f"asset_{i}.dat" for i in range(50)]) _touch(tmp_path, *[f"asset_{i}.dat" for i in range(50)])
composition = determine_programming_language_composition(str(tmp_path)) composition = compute_language_server_support_composition(str(tmp_path))
assert composition[Language.PYTHON] == 75.0 assert composition[LanguageServerId.PYTHON] == 75.0
assert composition[Language.GO] == 25.0 assert composition[LanguageServerId.GO] == 25.0
def test_repo_without_recognised_files(self, tmp_path: Path) -> None: def test_repo_without_recognised_files(self, tmp_path: Path) -> None:
_touch(tmp_path, "readme.txt", "logo.png") _touch(tmp_path, "readme.txt", "logo.png")
assert determine_programming_language_composition(str(tmp_path)) == {} assert compute_language_server_support_composition(str(tmp_path)) == {}
def test_empty_repo(self, tmp_path: Path) -> None: def test_empty_repo(self, tmp_path: Path) -> None:
assert determine_programming_language_composition(str(tmp_path)) == {} assert compute_language_server_support_composition(str(tmp_path)) == {}
+15 -15
View File
@@ -3,7 +3,7 @@ from pathlib import Path
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@@ -24,14 +24,14 @@ class TestAdaLanguageServer:
obviously test-affecting. obviously test-affecting.
""" """
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ADA], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
assert language_server.is_running() assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve() assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ADA], indirect=True)
def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
# main.adb (1-indexed source / 0-indexed LSP): # main.adb (1-indexed source / 0-indexed LSP):
# 5/4: Greeting : constant String := Helper.Greet ("Ada"); # 5/4: Greeting : constant String := Helper.Greet ("Ada");
@@ -47,8 +47,8 @@ class TestAdaLanguageServer:
# `Greeting` is declared on LSP line 4; ALS points at the identifier (column 3). # `Greeting` is declared on LSP line 4; ALS points at the identifier (column 3).
assert loc["range"]["start"]["line"] == 4 assert loc["range"]["start"]["line"] == 4
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ADA], indirect=True)
def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
# main.adb LSP line 4 / column 40 sits on the `G` of `Helper.Greet`: # main.adb LSP line 4 / column 40 sits on the `G` of `Helper.Greet`:
# ` Greeting : constant String := Helper.Greet ("Ada");` # ` Greeting : constant String := Helper.Greet ("Ada");`
@@ -65,8 +65,8 @@ class TestAdaLanguageServer:
assert loc["range"]["start"]["line"] == 4 assert loc["range"]["start"]["line"] == 4
assert loc["range"]["start"]["character"] == 12 assert loc["range"]["start"]["character"] == 12
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ADA], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
# Click on `Helper` in `with Helper;` (main.adb LSP line 1, column 5). # Click on `Helper` in `with Helper;` (main.adb LSP line 1, column 5).
# Serena's request_references uses includeDeclaration=False, so we need a symbol with # Serena's request_references uses includeDeclaration=False, so we need a symbol with
@@ -80,8 +80,8 @@ class TestAdaLanguageServer:
# The qualifier usage on LSP line 4 of main.adb must appear among the references. # The qualifier usage on LSP line 4 of main.adb must appear among the references.
assert 4 in ref_lines_in_main, f"Expected reference on line 4 of main.adb, got {ref_lines_in_main}" assert 4 in ref_lines_in_main, f"Expected reference on line 4 of main.adb, got {ref_lines_in_main}"
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ADA], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
# Click on the `G` of `Greet` in the spec: # Click on the `G` of `Greet` in the spec:
# helper.ads LSP line 4: ` function Greet (Name : String) return String;` # helper.ads LSP line 4: ` function Greet (Name : String) return String;`
@@ -94,7 +94,7 @@ class TestAdaLanguageServer:
# The call site in main.adb must appear in the references. # The call site in main.adb must appear in the references.
assert "main.adb" in ref_files, f"Expected reference in main.adb, got {ref_files}" assert "main.adb" in ref_files, f"Expected reference in main.adb, got {ref_files}"
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
@@ -103,7 +103,7 @@ class TestAdaLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "Greet"), "Greet subprogram not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Greet"), "Greet subprogram not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main procedure not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main procedure not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
def test_document_symbols_helper(self, language_server: SolidLanguageServer) -> None: def test_document_symbols_helper(self, language_server: SolidLanguageServer) -> None:
doc_symbols = language_server.request_document_symbols(str(Path("src") / "helper.ads")) doc_symbols = language_server.request_document_symbols(str(Path("src") / "helper.ads"))
all_symbols, _ = doc_symbols.get_all_symbols_and_roots() all_symbols, _ = doc_symbols.get_all_symbols_and_roots()
@@ -111,7 +111,7 @@ class TestAdaLanguageServer:
assert "Helper" in names, f"Helper package not found in helper.ads document symbols. Found: {names}" assert "Helper" in names, f"Helper package not found in helper.ads document symbols. Found: {names}"
assert "Greet" in names, f"Greet not found in helper.ads document symbols. Found: {names}" assert "Greet" in names, f"Greet not found in helper.ads document symbols. Found: {names}"
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
def test_document_symbols_hierarchical_structure(self, language_server: SolidLanguageServer) -> None: def test_document_symbols_hierarchical_structure(self, language_server: SolidLanguageServer) -> None:
"""ALS must return hierarchical DocumentSymbol[] with subprograms nested under their package.""" """ALS must return hierarchical DocumentSymbol[] with subprograms nested under their package."""
all_symbols, root_symbols = language_server.request_document_symbols(str(Path("src") / "helper.ads")).get_all_symbols_and_roots() all_symbols, root_symbols = language_server.request_document_symbols(str(Path("src") / "helper.ads")).get_all_symbols_and_roots()
@@ -129,7 +129,7 @@ class TestAdaLanguageServer:
# Greet must NOT appear at root level — that would indicate the flat fallback format. # Greet must NOT appear at root level — that would indicate the flat fallback format.
assert "Greet" not in root_names, f"Greet should be a child of Helper, not at root level. Roots: {root_names}" assert "Greet" not in root_names, f"Greet should be a child of Helper, not at root level. Roots: {root_names}"
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None: def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
# ALS surfaces a few Ada-specific synthetic groupings as symbols: # ALS surfaces a few Ada-specific synthetic groupings as symbols:
# - "With clauses" — namespace-kind group containing all `with` statements in a unit # - "With clauses" — namespace-kind group containing all `with` statements in a unit
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.ada @pytest.mark.ada
class TestAdaDiagnostics: class TestAdaDiagnostics:
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ADA], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+27 -27
View File
@@ -5,13 +5,13 @@ import pytest
from serena.symbol import LanguageServerSymbol from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.language_servers.al_language_server import ALLanguageServer from solidlsp.language_servers.al_language_server import ALLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols 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")] pytestmark = [pytest.mark.al, pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.AL), reason="AL tests are disabled")]
class TestExtractALDisplayName: class TestExtractALDisplayName:
@@ -62,7 +62,7 @@ class TestExtractALDisplayName:
@pytest.mark.al @pytest.mark.al
class TestALLanguageServer: class TestALLanguageServer:
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_symbol_names_are_normalized(self, language_server: SolidLanguageServer) -> None: def test_symbol_names_are_normalized(self, language_server: SolidLanguageServer) -> None:
"""Test that AL symbol names are normalized (metadata stripped).""" """Test that AL symbol names are normalized (metadata stripped)."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -79,7 +79,7 @@ class TestALLanguageServer:
# Name should be just "TEST Customer", not "Table 50000 'TEST Customer'" # Name should be just "TEST Customer", not "Table 50000 'TEST Customer'"
assert customer_table["name"] == "TEST Customer", f"Expected normalized name 'TEST Customer', got '{customer_table['name']}'" assert customer_table["name"] == "TEST Customer", f"Expected normalized name 'TEST Customer', got '{customer_table['name']}'"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_find_symbol_exact_match(self, language_server: SolidLanguageServer) -> None: def test_find_symbol_exact_match(self, language_server: SolidLanguageServer) -> None:
"""Test that find_symbol can match AL symbols by normalized name without substring_matching.""" """Test that find_symbol can match AL symbols by normalized name without substring_matching."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -96,7 +96,7 @@ class TestALLanguageServer:
pytest.fail("Could not find 'TEST Customer' symbol by exact name match") pytest.fail("Could not find 'TEST Customer' symbol by exact name match")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_find_codeunit_exact_match(self, language_server: SolidLanguageServer) -> None: def test_find_codeunit_exact_match(self, language_server: SolidLanguageServer) -> None:
"""Test finding a codeunit by its normalized name.""" """Test finding a codeunit by its normalized name."""
file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al") file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al")
@@ -112,7 +112,7 @@ class TestALLanguageServer:
pytest.fail("Could not find 'CustomerMgt' symbol by exact name match") pytest.fail("Could not find 'CustomerMgt' symbol by exact name match")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test that AL Language Server can find symbols in the test repository with normalized names.""" """Test that AL Language Server can find symbols in the test repository with normalized names."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -136,7 +136,7 @@ class TestALLanguageServer:
# Check for interface symbol # Check for interface symbol
assert SymbolUtils.symbol_tree_contains_name(symbols, "IPaymentProcessor"), "IPaymentProcessor interface not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "IPaymentProcessor"), "IPaymentProcessor interface not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_find_table_fields(self, language_server: SolidLanguageServer) -> None: def test_find_table_fields(self, language_server: SolidLanguageServer) -> None:
"""Test that AL Language Server can find fields within a table.""" """Test that AL Language Server can find fields within a table."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -169,7 +169,7 @@ class TestALLanguageServer:
assert any("Name" in name for name in field_names), f"Name field not found. Fields: {field_names}" assert any("Name" in name for name in field_names), f"Name field not found. Fields: {field_names}"
assert any("Balance" in name for name in field_names), f"Balance field not found. Fields: {field_names}" assert any("Balance" in name for name in field_names), f"Balance field not found. Fields: {field_names}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_find_procedures(self, language_server: SolidLanguageServer) -> None: def test_find_procedures(self, language_server: SolidLanguageServer) -> None:
"""Test that AL Language Server can find procedures in codeunits.""" """Test that AL Language Server can find procedures in codeunits."""
file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al") file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al")
@@ -191,7 +191,7 @@ class TestALLanguageServer:
assert any("CreateCustomer" in name for name in procedure_names), "CreateCustomer procedure not found" assert any("CreateCustomer" in name for name in procedure_names), "CreateCustomer procedure not found"
assert any("TestNoSeries" in name for name in procedure_names), "TestNoSeries procedure not found" assert any("TestNoSeries" in name for name in procedure_names), "TestNoSeries procedure not found"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test that AL Language Server can find references to symbols.""" """Test that AL Language Server can find references to symbols."""
# Find references to the Customer table from the CustomerMgt codeunit # Find references to the Customer table from the CustomerMgt codeunit
@@ -220,7 +220,7 @@ class TestALLanguageServer:
"Customer table should be referenced in CustomerCard.Page.al" "Customer table should be referenced in CustomerCard.Page.al"
) )
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_cross_file_symbols(self, language_server: SolidLanguageServer) -> None: def test_cross_file_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test that AL Language Server can handle cross-file symbol relationships.""" """Test that AL Language Server can handle cross-file symbol relationships."""
# Get all symbols to verify cross-file visibility # Get all symbols to verify cross-file visibility
@@ -290,7 +290,7 @@ class TestALHoverInjection:
return hover, None return hover, None
return None, None return None, None
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_table_injects_full_name(self, language_server: SolidLanguageServer) -> None: def test_hover_table_injects_full_name(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over a Table symbol shows the full object name with ID.""" """Test that hovering over a Table symbol shows the full object name with ID."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -300,7 +300,7 @@ class TestALHoverInjection:
assert value is not None, "Hover should have content" assert value is not None, "Hover should have content"
assert '**Table 50000 "TEST Customer"**' in value, f"Hover should contain full Table name with ID. Got: {value[:200]}" assert '**Table 50000 "TEST Customer"**' in value, f"Hover should contain full Table name with ID. Got: {value[:200]}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_page_injects_full_name(self, language_server: SolidLanguageServer) -> None: def test_hover_page_injects_full_name(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over a Page symbol shows the full object name with ID.""" """Test that hovering over a Page symbol shows the full object name with ID."""
file_path = os.path.join("src", "Pages", "CustomerCard.Page.al") file_path = os.path.join("src", "Pages", "CustomerCard.Page.al")
@@ -310,7 +310,7 @@ class TestALHoverInjection:
assert value is not None, "Hover should have content" assert value is not None, "Hover should have content"
assert '**Page 50001 "TEST Customer Card"**' in value, f"Hover should contain full Page name with ID. Got: {value[:200]}" assert '**Page 50001 "TEST Customer Card"**' in value, f"Hover should contain full Page name with ID. Got: {value[:200]}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_codeunit_injects_full_name(self, language_server: SolidLanguageServer) -> None: def test_hover_codeunit_injects_full_name(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over a Codeunit symbol shows the full object name with ID.""" """Test that hovering over a Codeunit symbol shows the full object name with ID."""
file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al") file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al")
@@ -320,7 +320,7 @@ class TestALHoverInjection:
assert value is not None, "Hover should have content" assert value is not None, "Hover should have content"
assert "**Codeunit 50000 CustomerMgt**" in value, f"Hover should contain full Codeunit name with ID. Got: {value[:200]}" assert "**Codeunit 50000 CustomerMgt**" in value, f"Hover should contain full Codeunit name with ID. Got: {value[:200]}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_enum_injects_full_name(self, language_server: SolidLanguageServer) -> None: def test_hover_enum_injects_full_name(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over an Enum symbol shows the full object name with ID.""" """Test that hovering over an Enum symbol shows the full object name with ID."""
file_path = os.path.join("src", "Enums", "CustomerType.Enum.al") file_path = os.path.join("src", "Enums", "CustomerType.Enum.al")
@@ -330,7 +330,7 @@ class TestALHoverInjection:
assert value is not None, "Hover should have content" assert value is not None, "Hover should have content"
assert "**Enum 50000 CustomerType**" in value, f"Hover should contain full Enum name with ID. Got: {value[:200]}" assert "**Enum 50000 CustomerType**" in value, f"Hover should contain full Enum name with ID. Got: {value[:200]}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_interface_injects_full_name(self, language_server: SolidLanguageServer) -> None: def test_hover_interface_injects_full_name(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over an Interface symbol shows the full object name (no ID for interfaces).""" """Test that hovering over an Interface symbol shows the full object name (no ID for interfaces)."""
file_path = os.path.join("src", "Interfaces", "IPaymentProcessor.Interface.al") file_path = os.path.join("src", "Interfaces", "IPaymentProcessor.Interface.al")
@@ -340,7 +340,7 @@ class TestALHoverInjection:
assert value is not None, "Hover should have content" assert value is not None, "Hover should have content"
assert "**Interface IPaymentProcessor**" in value, f"Hover should contain full Interface name. Got: {value[:200]}" assert "**Interface IPaymentProcessor**" in value, f"Hover should contain full Interface name. Got: {value[:200]}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_procedure_no_injection(self, language_server: SolidLanguageServer) -> None: def test_hover_procedure_no_injection(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over a procedure does NOT inject object name (procedures are not normalized).""" """Test that hovering over a procedure does NOT inject object name (procedures are not normalized)."""
file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al") file_path = os.path.join("src", "Codeunits", "CustomerMgt.Codeunit.al")
@@ -353,7 +353,7 @@ class TestALHoverInjection:
# But should contain procedure info # But should contain procedure info
assert "CreateCustomer" in value, f"Hover should contain procedure name. Got: {value[:200]}" assert "CreateCustomer" in value, f"Hover should contain procedure name. Got: {value[:200]}"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_field_no_injection(self, language_server: SolidLanguageServer) -> None: def test_hover_field_no_injection(self, language_server: SolidLanguageServer) -> None:
"""Test that hovering over a field does NOT inject object name (fields are not normalized).""" """Test that hovering over a field does NOT inject object name (fields are not normalized)."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -380,7 +380,7 @@ class TestALHoverInjection:
pytest.fail("Could not find a field to test hover on") pytest.fail("Could not find a field to test hover on")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_multiple_objects_correct_injection(self, language_server: SolidLanguageServer) -> None: def test_hover_multiple_objects_correct_injection(self, language_server: SolidLanguageServer) -> None:
"""Test that multiple AL objects each get their correct full name injected.""" """Test that multiple AL objects each get their correct full name injected."""
test_cases = [ test_cases = [
@@ -398,7 +398,7 @@ class TestALHoverInjection:
f"Hover for {symbol_name} should contain '{expected_full_name}'. Got: {value[:200]}" f"Hover for {symbol_name} should contain '{expected_full_name}'. Got: {value[:200]}"
) )
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_contains_separator_after_injection(self, language_server: SolidLanguageServer) -> None: def test_hover_contains_separator_after_injection(self, language_server: SolidLanguageServer) -> None:
"""Test that injected hover has a separator between injected name and original content.""" """Test that injected hover has a separator between injected name and original content."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -413,7 +413,7 @@ class TestALHoverInjection:
separator_pos = value.find("---") separator_pos = value.find("---")
assert separator_pos > bold_end, "Separator should come after the injected name" assert separator_pos > bold_end, "Separator should come after the injected name"
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_preserves_original_content(self, language_server: SolidLanguageServer) -> None: def test_hover_preserves_original_content(self, language_server: SolidLanguageServer) -> None:
"""Test that the original hover content is preserved after the injected name.""" """Test that the original hover content is preserved after the injected name."""
file_path = os.path.join("src", "Tables", "Customer.Table.al") file_path = os.path.join("src", "Tables", "Customer.Table.al")
@@ -430,7 +430,7 @@ class TestALHoverInjection:
class TestALPathNormalization: class TestALPathNormalization:
"""Tests for path normalization in hover injection cache.""" """Tests for path normalization in hover injection cache."""
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_with_forward_slash_path(self, language_server: SolidLanguageServer) -> None: def test_hover_with_forward_slash_path(self, language_server: SolidLanguageServer) -> None:
"""Test that hover injection works with forward slash paths.""" """Test that hover injection works with forward slash paths."""
file_path = "src/Tables/Customer.Table.al" file_path = "src/Tables/Customer.Table.al"
@@ -451,7 +451,7 @@ class TestALPathNormalization:
pytest.fail("Could not find TEST Customer symbol") pytest.fail("Could not find TEST Customer symbol")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_with_backslash_path(self, language_server: SolidLanguageServer) -> None: def test_hover_with_backslash_path(self, language_server: SolidLanguageServer) -> None:
"""Test that hover injection works with backslash paths (Windows style).""" """Test that hover injection works with backslash paths (Windows style)."""
file_path = "src\\Tables\\Customer.Table.al" file_path = "src\\Tables\\Customer.Table.al"
@@ -472,7 +472,7 @@ class TestALPathNormalization:
pytest.fail("Could not find TEST Customer symbol") pytest.fail("Could not find TEST Customer symbol")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_with_mixed_path_formats_symbols_backslash_hover_forward(self, language_server: SolidLanguageServer) -> None: def test_hover_with_mixed_path_formats_symbols_backslash_hover_forward(self, language_server: SolidLanguageServer) -> None:
"""Test hover works when symbols requested with backslash but hover with forward slash.""" """Test hover works when symbols requested with backslash but hover with forward slash."""
file_path_backslash = "src\\Tables\\Customer.Table.al" file_path_backslash = "src\\Tables\\Customer.Table.al"
@@ -499,7 +499,7 @@ class TestALPathNormalization:
pytest.fail("Could not find TEST Customer symbol") pytest.fail("Could not find TEST Customer symbol")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_with_mixed_path_formats_symbols_forward_hover_backslash(self, language_server: SolidLanguageServer) -> None: def test_hover_with_mixed_path_formats_symbols_forward_hover_backslash(self, language_server: SolidLanguageServer) -> None:
"""Test hover works when symbols requested with forward slash but hover with backslash.""" """Test hover works when symbols requested with forward slash but hover with backslash."""
file_path_forward = "src/Tables/Customer.Table.al" file_path_forward = "src/Tables/Customer.Table.al"
@@ -526,7 +526,7 @@ class TestALPathNormalization:
pytest.fail("Could not find TEST Customer symbol") pytest.fail("Could not find TEST Customer symbol")
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_hover_caching_multiple_files_different_path_formats(self, language_server: SolidLanguageServer) -> None: def test_hover_caching_multiple_files_different_path_formats(self, language_server: SolidLanguageServer) -> None:
"""Test that hover injection cache works correctly across multiple files with different path formats.""" """Test that hover injection cache works correctly across multiple files with different path formats."""
test_cases = [ test_cases = [
@@ -559,7 +559,7 @@ class TestALPathNormalization:
) )
break break
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.AL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+20 -20
View File
@@ -17,19 +17,19 @@ import pytest
from serena.util.text_utils import find_text_coordinates from serena.util.text_utils import find_text_coordinates
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.conftest import read_repo_file, request_all_symbols from test.solidlsp.conftest import read_repo_file, request_all_symbols
@pytest.mark.angular @pytest.mark.angular
class TestAngularLanguageServerBasics: class TestAngularLanguageServerBasics:
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ANGULAR], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
assert language_server.is_running() assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve() assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_component_class_symbols(self, language_server: SolidLanguageServer) -> None: def test_component_class_symbols(self, language_server: SolidLanguageServer) -> None:
"""The Angular LS exposes the component class methods/fields via tsserver.""" """The Angular LS exposes the component class methods/fields via tsserver."""
all_symbols, _ = language_server.request_document_symbols("src/app/app.component.ts").get_all_symbols_and_roots() all_symbols, _ = language_server.request_document_symbols("src/app/app.component.ts").get_all_symbols_and_roots()
@@ -37,14 +37,14 @@ class TestAngularLanguageServerBasics:
for expected in ("AppComponent", "title", "userName", "items", "greeting", "setName"): for expected in ("AppComponent", "title", "userName", "items", "greeting", "setName"):
assert expected in names, f"Expected '{expected}' in component symbols: {names}" assert expected in names, f"Expected '{expected}' in component symbols: {names}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_service_class_symbols(self, language_server: SolidLanguageServer) -> None: def test_service_class_symbols(self, language_server: SolidLanguageServer) -> None:
all_symbols, _ = language_server.request_document_symbols("src/app/greeting.service.ts").get_all_symbols_and_roots() all_symbols, _ = language_server.request_document_symbols("src/app/greeting.service.ts").get_all_symbols_and_roots()
names = [s["name"] for s in all_symbols] names = [s["name"] for s in all_symbols]
for expected in ("GreetingService", "greet", "defaultName"): for expected in ("GreetingService", "greet", "defaultName"):
assert expected in names, f"Expected '{expected}' in service symbols: {names}" assert expected in names, f"Expected '{expected}' in service symbols: {names}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_full_symbol_tree_includes_all_files(self, language_server: SolidLanguageServer) -> None: def test_full_symbol_tree_includes_all_files(self, language_server: SolidLanguageServer) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
relative_paths = {s.get("location", {}).get("relativePath") for s in all_symbols} relative_paths = {s.get("location", {}).get("relativePath") for s in all_symbols}
@@ -59,7 +59,7 @@ class TestAngularLanguageServerBasics:
): ):
assert f in relative_paths, f"Expected {f} to appear in symbol tree, got {relative_paths}" assert f in relative_paths, f"Expected {f} to appear in symbol tree, got {relative_paths}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_template_definition_to_component_method(self, language_server: SolidLanguageServer) -> None: def test_template_definition_to_component_method(self, language_server: SolidLanguageServer) -> None:
"""Resolve `greeting()` interpolation in the template to its component method. """Resolve `greeting()` interpolation in the template to its component method.
@@ -79,7 +79,7 @@ class TestAngularLanguageServerBasics:
f"Expected definition to resolve into app.component.ts, got URIs: {target_uris}" f"Expected definition to resolve into app.component.ts, got URIs: {target_uris}"
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_plain_html_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_plain_html_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""DocumentSymbol on plain index.html should come back from the HTML companion. """DocumentSymbol on plain index.html should come back from the HTML companion.
@@ -94,7 +94,7 @@ class TestAngularLanguageServerBasics:
for expected in ("html", "head", "body", "app-root"): for expected in ("html", "head", "body", "app-root"):
assert expected in names, f"Expected '{expected}' in plain-HTML symbol list, got: {names}" assert expected in names, f"Expected '{expected}' in plain-HTML symbol list, got: {names}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_template_html_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_template_html_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""DocumentSymbol on an Angular template must return the HTML element tree. """DocumentSymbol on an Angular template must return the HTML element tree.
@@ -108,7 +108,7 @@ class TestAngularLanguageServerBasics:
for expected in ("section", "h1", "p", "input", "ul", "app-item-card"): for expected in ("section", "h1", "p", "input", "ul", "app-item-card"):
assert expected in names, f"Expected '{expected}' in template HTML symbol list, got: {names}" assert expected in names, f"Expected '{expected}' in template HTML symbol list, got: {names}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_ts_method_references_include_template_usage(self, language_server: SolidLanguageServer) -> None: def test_ts_method_references_include_template_usage(self, language_server: SolidLanguageServer) -> None:
"""References on a .ts component method must include its template callers. """References on a .ts component method must include its template callers.
@@ -134,7 +134,7 @@ class TestAngularLanguageServerBasics:
class TestAngularHover: class TestAngularHover:
"""Hover routing — .ts goes to the companion tsserver, .html goes to ngserver.""" """Hover routing — .ts goes to the companion tsserver, .html goes to ngserver."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_hover_on_ts_method(self, language_server: SolidLanguageServer) -> None: def test_hover_on_ts_method(self, language_server: SolidLanguageServer) -> None:
"""Hover on a .ts method declaration is routed through the companion TS server """Hover on a .ts method declaration is routed through the companion TS server
and must yield a non-empty MarkupContent describing the method signature. and must yield a non-empty MarkupContent describing the method signature.
@@ -153,7 +153,7 @@ class TestAngularHover:
text = contents["value"] if isinstance(contents, dict) else str(contents) text = contents["value"] if isinstance(contents, dict) else str(contents)
assert "setName" in text, f"Expected setName in hover text, got: {text}" assert "setName" in text, f"Expected setName in hover text, got: {text}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_hover_on_template_method_call(self, language_server: SolidLanguageServer) -> None: def test_hover_on_template_method_call(self, language_server: SolidLanguageServer) -> None:
"""Hover on a method call inside an Angular template ({{ greeting() }}) goes """Hover on a method call inside an Angular template ({{ greeting() }}) goes
through ngserver and must yield Angular-aware type info. through ngserver and must yield Angular-aware type info.
@@ -174,7 +174,7 @@ class TestAngularHover:
class TestAngularDefinitionRouting: class TestAngularDefinitionRouting:
"""Cross-file definition for the binding flavours not covered by basic tests.""" """Cross-file definition for the binding flavours not covered by basic tests."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_from_property_binding(self, language_server: SolidLanguageServer) -> None: def test_definition_from_property_binding(self, language_server: SolidLanguageServer) -> None:
"""Property binding ``[value]="userName()"`` must resolve to the component's """Property binding ``[value]="userName()"`` must resolve to the component's
``userName`` signal field declaration in app.component.ts. ``userName`` signal field declaration in app.component.ts.
@@ -193,7 +193,7 @@ class TestAngularDefinitionRouting:
f"Expected definition to resolve into app.component.ts, got URIs: {target_uris}" f"Expected definition to resolve into app.component.ts, got URIs: {target_uris}"
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_from_event_binding(self, language_server: SolidLanguageServer) -> None: def test_definition_from_event_binding(self, language_server: SolidLanguageServer) -> None:
"""Event binding ``(input)="setName(...)"`` must resolve to the component's """Event binding ``(input)="setName(...)"`` must resolve to the component's
``setName`` method declaration in app.component.ts. ``setName`` method declaration in app.component.ts.
@@ -211,7 +211,7 @@ class TestAngularDefinitionRouting:
f"Expected definition to resolve into app.component.ts, got URIs: {target_uris}" f"Expected definition to resolve into app.component.ts, got URIs: {target_uris}"
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_service_import_in_component(self, language_server: SolidLanguageServer) -> None: def test_definition_service_import_in_component(self, language_server: SolidLanguageServer) -> None:
"""The ``GreetingService`` symbol used in the constructor parameter list """The ``GreetingService`` symbol used in the constructor parameter list
of AppComponent must resolve to greeting.service.ts via the companion TS server. of AppComponent must resolve to greeting.service.ts via the companion TS server.
@@ -231,7 +231,7 @@ class TestAngularDefinitionRouting:
f"Expected definition to resolve into greeting.service.ts, got URIs: {target_uris}" f"Expected definition to resolve into greeting.service.ts, got URIs: {target_uris}"
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_from_child_component_selector(self, language_server: SolidLanguageServer) -> None: def test_definition_from_child_component_selector(self, language_server: SolidLanguageServer) -> None:
"""The ``<app-item-card>`` element in the parent template must resolve to the """The ``<app-item-card>`` element in the parent template must resolve to the
ItemCardComponent class declaration in item-card.component.ts. ItemCardComponent class declaration in item-card.component.ts.
@@ -258,7 +258,7 @@ class TestAngularDefinitionRouting:
class TestAngularRename: class TestAngularRename:
"""Rename routing returns a WorkspaceEdit without applying it (safe for fixtures).""" """Rename routing returns a WorkspaceEdit without applying it (safe for fixtures)."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_rename_method_returns_edits_for_ts_and_template(self, language_server: SolidLanguageServer) -> None: def test_rename_method_returns_edits_for_ts_and_template(self, language_server: SolidLanguageServer) -> None:
"""Renaming the ``setName`` method from its .ts declaration must return a """Renaming the ``setName`` method from its .ts declaration must return a
WorkspaceEdit that touches both app.component.ts (declaration + any TS calls) WorkspaceEdit that touches both app.component.ts (declaration + any TS calls)
@@ -289,7 +289,7 @@ class TestAngularRename:
class TestAngularSymbolStructure: class TestAngularSymbolStructure:
"""Hierarchical symbol structure — class symbols must contain method/field children.""" """Hierarchical symbol structure — class symbols must contain method/field children."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_component_class_has_methods_and_fields_as_children(self, language_server: SolidLanguageServer) -> None: def test_component_class_has_methods_and_fields_as_children(self, language_server: SolidLanguageServer) -> None:
"""``request_document_symbols`` should return AppComponent as a class symbol """``request_document_symbols`` should return AppComponent as a class symbol
whose children include its methods (``greeting``, ``setName``) and signal whose children include its methods (``greeting``, ``setName``) and signal
@@ -306,7 +306,7 @@ class TestAngularSymbolStructure:
for expected in ("greeting", "setName", "title", "userName", "items"): for expected in ("greeting", "setName", "title", "userName", "items"):
assert expected in child_names, f"Expected '{expected}' as child of AppComponent, got: {child_names}" assert expected in child_names, f"Expected '{expected}' as child of AppComponent, got: {child_names}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_pipe_class_in_symbol_tree(self, language_server: SolidLanguageServer) -> None: def test_pipe_class_in_symbol_tree(self, language_server: SolidLanguageServer) -> None:
"""A custom pipe (``ExclaimPipe``) declared in exclaim.pipe.ts must appear in """A custom pipe (``ExclaimPipe``) declared in exclaim.pipe.ts must appear in
the workspace symbol tree alongside the components and the service. the workspace symbol tree alongside the components and the service.
@@ -321,7 +321,7 @@ class TestAngularSymbolStructure:
class TestAngularImplementations: class TestAngularImplementations:
"""``textDocument/implementation`` is routed through tsserver via the Angular plugin.""" """``textDocument/implementation`` is routed through tsserver via the Angular plugin."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_find_implementations_of_interface_method(self, language_server: SolidLanguageServer) -> None: def test_find_implementations_of_interface_method(self, language_server: SolidLanguageServer) -> None:
"""``Greeter.greet`` is implemented by ``GreetingService``; ``request_implementation`` """``Greeter.greet`` is implemented by ``GreetingService``; ``request_implementation``
invoked at the interface declaration must point at the service. invoked at the interface declaration must point at the service.
@@ -12,13 +12,13 @@ Two paths are exercised:
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.angular @pytest.mark.angular
class TestAngularDiagnostics: class TestAngularDiagnostics:
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_component_class_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_component_class_diagnostics(self, language_server: SolidLanguageServer) -> None:
"""The component's ``count: number = 'not-a-number'`` must be flagged by tsserver.""" """The component's ``count: number = 'not-a-number'`` must be flagged by tsserver."""
assert_file_diagnostics( assert_file_diagnostics(
@@ -28,7 +28,7 @@ class TestAngularDiagnostics:
min_count=1, min_count=1,
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_template_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_template_diagnostics(self, language_server: SolidLanguageServer) -> None:
"""The template's ``{{ undefinedSignal() }}`` must be flagged by ngserver. """The template's ``{{ undefinedSignal() }}`` must be flagged by ngserver.
@@ -21,7 +21,7 @@ import time
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_exceptions import SolidLSPException
from test.conftest import _create_ls from test.conftest import _create_ls
@@ -37,7 +37,7 @@ TEMPLATE_FILE = os.path.join("src", "app", "app.component.html")
class TestAngularInvalidPositionsOnTs: class TestAngularInvalidPositionsOnTs:
"""Negative / out-of-bounds line and column on .ts files (TS-companion route).""" """Negative / out-of-bounds line and column on .ts files (TS-companion route)."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_negative_line_number_containing_symbol(self, language_server: SolidLanguageServer) -> None: def test_negative_line_number_containing_symbol(self, language_server: SolidLanguageServer) -> None:
"""``request_containing_symbol`` short-circuits before reaching the LS: """``request_containing_symbol`` short-circuits before reaching the LS:
a negative line returns None. a negative line returns None.
@@ -45,19 +45,19 @@ class TestAngularInvalidPositionsOnTs:
result = language_server.request_containing_symbol(TS_FILE, -1, 0) result = language_server.request_containing_symbol(TS_FILE, -1, 0)
assert result is None, f"Expected None for negative line, got: {result!r}" assert result is None, f"Expected None for negative line, got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_negative_character_number_containing_symbol(self, language_server: SolidLanguageServer) -> None: def test_negative_character_number_containing_symbol(self, language_server: SolidLanguageServer) -> None:
result = language_server.request_containing_symbol(TS_FILE, 5, -1) result = language_server.request_containing_symbol(TS_FILE, 5, -1)
assert result is None, f"Expected None for negative character, got: {result!r}" assert result is None, f"Expected None for negative character, got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_line_number_beyond_file_length(self, language_server: SolidLanguageServer) -> None: def test_line_number_beyond_file_length(self, language_server: SolidLanguageServer) -> None:
"""The wrapper code raises ``IndexError`` before reaching the LS.""" """The wrapper code raises ``IndexError`` before reaching the LS."""
with pytest.raises(IndexError) as exc_info: with pytest.raises(IndexError) as exc_info:
language_server.request_containing_symbol(TS_FILE, 99999, 0) language_server.request_containing_symbol(TS_FILE, 99999, 0)
assert "list index out of range" in str(exc_info.value), f"Expected 'list index out of range' error, got: {exc_info.value}" assert "list index out of range" in str(exc_info.value), f"Expected 'list index out of range' error, got: {exc_info.value}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_character_beyond_line_length_returns_enclosing_class(self, language_server: SolidLanguageServer) -> None: def test_character_beyond_line_length_returns_enclosing_class(self, language_server: SolidLanguageServer) -> None:
"""Character far beyond end-of-line is clamped by the LSP. Line 5 of """Character far beyond end-of-line is clamped by the LSP. Line 5 of
app.component.ts is inside ``export class AppComponent { ... }``, so the app.component.ts is inside ``export class AppComponent { ... }``, so the
@@ -69,7 +69,7 @@ class TestAngularInvalidPositionsOnTs:
assert isinstance(result, dict), f"Expected dict (containing class), got: {result!r}" assert isinstance(result, dict), f"Expected dict (containing class), got: {result!r}"
assert result.get("name") == "AppComponent", f"Expected containing symbol 'AppComponent', got: {result.get('name')!r}" assert result.get("name") == "AppComponent", f"Expected containing symbol 'AppComponent', got: {result.get('name')!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_references_at_negative_line_returns_empty(self, language_server: SolidLanguageServer) -> None: def test_references_at_negative_line_returns_empty(self, language_server: SolidLanguageServer) -> None:
"""The Angular LS's TS companion returns ``[]`` for negative-line """The Angular LS's TS companion returns ``[]`` for negative-line
``request_references`` — it does **not** raise. (Plain Vue tests ``request_references`` — it does **not** raise. (Plain Vue tests
@@ -79,7 +79,7 @@ class TestAngularInvalidPositionsOnTs:
result = language_server.request_references(TS_FILE, -1, 0) result = language_server.request_references(TS_FILE, -1, 0)
assert result == [], f"Expected [], got: {result!r}" assert result == [], f"Expected [], got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_at_negative_position_returns_empty(self, language_server: SolidLanguageServer) -> None: def test_definition_at_negative_position_returns_empty(self, language_server: SolidLanguageServer) -> None:
"""Same as references: TS companion returns ``[]``, does not raise.""" """Same as references: TS companion returns ``[]``, does not raise."""
result = language_server.request_definition(TS_FILE, -1, 0) result = language_server.request_definition(TS_FILE, -1, 0)
@@ -93,7 +93,7 @@ class TestAngularInvalidPositionsOnTs:
class TestAngularInvalidPositionsOnTemplate: class TestAngularInvalidPositionsOnTemplate:
"""Negative / out-of-bounds positions on .html files (ngserver route).""" """Negative / out-of-bounds positions on .html files (ngserver route)."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_negative_line_definition_raises(self, language_server: SolidLanguageServer) -> None: def test_negative_line_definition_raises(self, language_server: SolidLanguageServer) -> None:
"""Ngserver does **not** swallow malformed positions: it surfaces a """Ngserver does **not** swallow malformed positions: it surfaces a
``Debug Failure. False expression.`` error from its underlying ``Debug Failure. False expression.`` error from its underlying
@@ -105,7 +105,7 @@ class TestAngularInvalidPositionsOnTemplate:
f"Unexpected exception message: {exc_info.value}" f"Unexpected exception message: {exc_info.value}"
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_character_beyond_line_definition_raises(self, language_server: SolidLanguageServer) -> None: def test_character_beyond_line_definition_raises(self, language_server: SolidLanguageServer) -> None:
"""Same path as negative line — ngserver raises Debug Failure.""" """Same path as negative line — ngserver raises Debug Failure."""
with pytest.raises(SolidLSPException) as exc_info: with pytest.raises(SolidLSPException) as exc_info:
@@ -118,7 +118,7 @@ class TestAngularInvalidPositionsOnTemplate:
class TestAngularNonExistentFiles: class TestAngularNonExistentFiles:
"""Requests against files that don't exist must raise FileNotFoundError consistently.""" """Requests against files that don't exist must raise FileNotFoundError consistently."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_nonexistent_ts_file_raises(self, language_server: SolidLanguageServer) -> None: def test_nonexistent_ts_file_raises(self, language_server: SolidLanguageServer) -> None:
nonexistent = os.path.join("src", "app", "does-not-exist.component.ts") nonexistent = os.path.join("src", "app", "does-not-exist.component.ts")
with pytest.raises(FileNotFoundError): with pytest.raises(FileNotFoundError):
@@ -128,7 +128,7 @@ class TestAngularNonExistentFiles:
with pytest.raises(FileNotFoundError): with pytest.raises(FileNotFoundError):
language_server.request_document_symbols(nonexistent) language_server.request_document_symbols(nonexistent)
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_nonexistent_html_file_raises(self, language_server: SolidLanguageServer) -> None: def test_nonexistent_html_file_raises(self, language_server: SolidLanguageServer) -> None:
nonexistent = os.path.join("src", "app", "does-not-exist.component.html") nonexistent = os.path.join("src", "app", "does-not-exist.component.html")
with pytest.raises(FileNotFoundError): with pytest.raises(FileNotFoundError):
@@ -140,7 +140,7 @@ class TestAngularNonExistentFiles:
class TestAngularUndefinedSymbols: class TestAngularUndefinedSymbols:
"""Symbols that have no callers / definitions / referencing positions.""" """Symbols that have no callers / definitions / referencing positions."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_at_keyword_position_returns_empty(self, language_server: SolidLanguageServer) -> None: def test_definition_at_keyword_position_returns_empty(self, language_server: SolidLanguageServer) -> None:
"""Cursor on the ``import`` keyword (line 0, col 0 of app.component.ts) """Cursor on the ``import`` keyword (line 0, col 0 of app.component.ts)
has no definition target — the TS companion returns ``[]``. has no definition target — the TS companion returns ``[]``.
@@ -148,7 +148,7 @@ class TestAngularUndefinedSymbols:
result = language_server.request_definition(TS_FILE, 0, 0) result = language_server.request_definition(TS_FILE, 0, 0)
assert result == [], f"Expected [] for keyword position, got: {result!r}" assert result == [], f"Expected [] for keyword position, got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_references_for_local_const_have_few_callers(self, language_server: SolidLanguageServer) -> None: def test_references_for_local_const_have_few_callers(self, language_server: SolidLanguageServer) -> None:
"""A locally-scoped private field has at most its declaration plus its """A locally-scoped private field has at most its declaration plus its
in-method use as references. ``defaultName`` on GreetingService is in-method use as references. ``defaultName`` on GreetingService is
@@ -168,7 +168,7 @@ class TestAngularUndefinedSymbols:
class TestAngularEdgeCasePositions: class TestAngularEdgeCasePositions:
"""Position (0,0), whitespace lines, and other boundary conditions.""" """Position (0,0), whitespace lines, and other boundary conditions."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_containing_symbol_at_file_start_is_none(self, language_server: SolidLanguageServer) -> None: def test_containing_symbol_at_file_start_is_none(self, language_server: SolidLanguageServer) -> None:
"""Line 0 of app.component.ts is an ``import`` statement, outside any """Line 0 of app.component.ts is an ``import`` statement, outside any
class or function — the TS companion returns ``None`` for containing class or function — the TS companion returns ``None`` for containing
@@ -177,19 +177,19 @@ class TestAngularEdgeCasePositions:
result = language_server.request_containing_symbol(TS_FILE, 0, 0) result = language_server.request_containing_symbol(TS_FILE, 0, 0)
assert result is None, f"Expected None at (0,0), got: {result!r}" assert result is None, f"Expected None at (0,0), got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_references_at_file_start_returns_empty(self, language_server: SolidLanguageServer) -> None: def test_references_at_file_start_returns_empty(self, language_server: SolidLanguageServer) -> None:
"""Position (0, 0) is on the ``import`` keyword — no references.""" """Position (0, 0) is on the ``import`` keyword — no references."""
result = language_server.request_references(TS_FILE, 0, 0) result = language_server.request_references(TS_FILE, 0, 0)
assert result == [], f"Expected [] at (0,0), got: {result!r}" assert result == [], f"Expected [] at (0,0), got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_definition_at_file_start_returns_empty(self, language_server: SolidLanguageServer) -> None: def test_definition_at_file_start_returns_empty(self, language_server: SolidLanguageServer) -> None:
"""Position (0, 0) is on the ``import`` keyword — no definition.""" """Position (0, 0) is on the ``import`` keyword — no definition."""
result = language_server.request_definition(TS_FILE, 0, 0) result = language_server.request_definition(TS_FILE, 0, 0)
assert result == [], f"Expected [] at (0,0), got: {result!r}" assert result == [], f"Expected [] at (0,0), got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_template_position_no_containing_symbol(self, language_server: SolidLanguageServer) -> None: def test_template_position_no_containing_symbol(self, language_server: SolidLanguageServer) -> None:
"""An Angular template has no class/function containers; the HTML """An Angular template has no class/function containers; the HTML
companion's documentSymbol provides element symbols only. companion's documentSymbol provides element symbols only.
@@ -198,7 +198,7 @@ class TestAngularEdgeCasePositions:
result = language_server.request_containing_symbol(TEMPLATE_FILE, 1, 4) result = language_server.request_containing_symbol(TEMPLATE_FILE, 1, 4)
assert result is None, f"Expected None inside template, got: {result!r}" assert result is None, f"Expected None inside template, got: {result!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_containing_symbol_inside_class_body_returns_class(self, language_server: SolidLanguageServer) -> None: def test_containing_symbol_inside_class_body_returns_class(self, language_server: SolidLanguageServer) -> None:
"""Lines 5 and 10 of app.component.ts are inside the AppComponent class """Lines 5 and 10 of app.component.ts are inside the AppComponent class
body. The TS companion correctly reports AppComponent as the body. The TS companion correctly reports AppComponent as the
@@ -209,7 +209,7 @@ class TestAngularEdgeCasePositions:
assert isinstance(result, dict), f"Line {line}: expected dict, got {result!r}" assert isinstance(result, dict), f"Line {line}: expected dict, got {result!r}"
assert result.get("name") == "AppComponent", f"Line {line}: expected 'AppComponent', got {result.get('name')!r}" assert result.get("name") == "AppComponent", f"Line {line}: expected 'AppComponent', got {result.get('name')!r}"
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_containing_symbol_outside_class_body_is_none(self, language_server: SolidLanguageServer) -> None: def test_containing_symbol_outside_class_body_is_none(self, language_server: SolidLanguageServer) -> None:
"""Line 0 (import) and line 15 (blank/whitespace inside file but """Line 0 (import) and line 15 (blank/whitespace inside file but
outside any symbol's range, depending on file structure) report no outside any symbol's range, depending on file structure) report no
@@ -223,7 +223,7 @@ class TestAngularEdgeCasePositions:
class TestAngularReferenceEdgeCases: class TestAngularReferenceEdgeCases:
"""Edge cases for the SolidLanguageServer reference helpers (built on top of LSP).""" """Edge cases for the SolidLanguageServer reference helpers (built on top of LSP)."""
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_referencing_symbols_at_invalid_position_raises(self, language_server: SolidLanguageServer) -> None: def test_referencing_symbols_at_invalid_position_raises(self, language_server: SolidLanguageServer) -> None:
"""Unlike ``request_references`` (which returns ``[]``), """Unlike ``request_references`` (which returns ``[]``),
``request_referencing_symbols`` validates more strictly and surfaces ``request_referencing_symbols`` validates more strictly and surfaces
@@ -241,7 +241,7 @@ class TestAngularReferenceEdgeCases:
f"Unexpected exception message: {exc_info.value}" f"Unexpected exception message: {exc_info.value}"
) )
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANGULAR], indirect=True)
def test_defining_symbol_at_invalid_position_returns_none(self, language_server: SolidLanguageServer) -> None: def test_defining_symbol_at_invalid_position_returns_none(self, language_server: SolidLanguageServer) -> None:
"""``request_defining_symbol`` short-circuits when no definition is """``request_defining_symbol`` short-circuits when no definition is
found and returns None — no exception, even for negative positions. found and returns None — no exception, even for negative positions.
@@ -328,7 +328,7 @@ class TestAngularStartupCleanup:
import psutil import psutil
ls = _create_ls(Language.ANGULAR) ls = _create_ls(LanguageServerId.ANGULAR)
my_proc = psutil.Process(os.getpid()) my_proc = psutil.Process(os.getpid())
children_before = {p.pid for p in my_proc.children(recursive=True)} children_before = {p.pid for p in my_proc.children(recursive=True)}
+10 -8
View File
@@ -11,24 +11,26 @@ from pathlib import Path
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(not language_tests_enabled(Language.ANSIBLE), reason="Ansible tests are disabled (no native Windows support)") @pytest.mark.skipif(
not language_server_tests_enabled(LanguageServerId.ANSIBLE), reason="Ansible tests are disabled (no native Windows support)"
)
@pytest.mark.ansible @pytest.mark.ansible
class TestAnsibleLanguageServerBasics: class TestAnsibleLanguageServerBasics:
"""Test basic Ansible language server functionality.""" """Test basic Ansible language server functionality."""
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANSIBLE], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.ANSIBLE], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ANSIBLE], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Language server starts and points to the correct repo.""" """Language server starts and points to the correct repo."""
assert language_server.is_running() assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve() assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANSIBLE], indirect=True)
def test_hover_on_module_contains_documentation(self, language_server: SolidLanguageServer) -> None: def test_hover_on_module_contains_documentation(self, language_server: SolidLanguageServer) -> None:
"""Hover on ansible.builtin.package returns module documentation.""" """Hover on ansible.builtin.package returns module documentation."""
# playbook.yml line 10 (0-indexed): "ansible.builtin.package:" # playbook.yml line 10 (0-indexed): "ansible.builtin.package:"
@@ -43,7 +45,7 @@ class TestAnsibleLanguageServerBasics:
hover_text = str(hover_value) hover_text = str(hover_value)
assert "package" in hover_text.lower(), f"Hover should mention 'package', got: {hover_text[:300]}" assert "package" in hover_text.lower(), f"Hover should mention 'package', got: {hover_text[:300]}"
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ANSIBLE], indirect=True)
def test_completions_contain_module_names(self, language_server: SolidLanguageServer) -> None: def test_completions_contain_module_names(self, language_server: SolidLanguageServer) -> None:
"""Completions at a task keyword position return Ansible module names.""" """Completions at a task keyword position return Ansible module names."""
# playbook.yml line 10 (0-indexed), col 6: inside a task block # playbook.yml line 10 (0-indexed), col 6: inside a task block
@@ -54,7 +56,7 @@ class TestAnsibleLanguageServerBasics:
assert labels, f"Expected completions with completionText, got: {result[:3]}" 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.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) @pytest.mark.parametrize("language_server", [LanguageServerId.ANSIBLE], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+8 -8
View File
@@ -8,7 +8,7 @@ like request_document_symbols using the bash test repository.
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@@ -16,13 +16,13 @@ from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name,
class TestBashLanguageServerBasics: class TestBashLanguageServerBasics:
"""Test basic functionality of the bash language server.""" """Test basic functionality of the bash language server."""
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_bash_language_server_initialization(self, language_server: SolidLanguageServer) -> None: def test_bash_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
"""Test that bash language server can be initialized successfully.""" """Test that bash language server can be initialized successfully."""
assert language_server is not None assert language_server is not None
assert language_server.language == Language.BASH assert language_server.ls_id == LanguageServerId.BASH
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_bash_request_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_bash_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test request_document_symbols for bash files.""" """Test request_document_symbols for bash files."""
# Test getting symbols from main.sh # Test getting symbols from main.sh
@@ -38,7 +38,7 @@ class TestBashLanguageServerBasics:
assert "main" in function_names, "Should find main function" assert "main" in function_names, "Should find main function"
assert len(function_symbols) >= 3, f"Should find at least 3 functions, found {len(function_symbols)}" assert len(function_symbols) >= 3, f"Should find at least 3 functions, found {len(function_symbols)}"
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_bash_request_document_symbols_with_body(self, language_server: SolidLanguageServer) -> None: def test_bash_request_document_symbols_with_body(self, language_server: SolidLanguageServer) -> None:
"""Test request_document_symbols with body extraction.""" """Test request_document_symbols with body extraction."""
# Test with include_body=True # Test with include_body=True
@@ -55,7 +55,7 @@ class TestBashLanguageServerBasics:
assert "function greet_user()" in body, "Function body should contain function definition" assert "function greet_user()" in body, "Function body should contain function definition"
assert "case" in body.lower(), "Function body should contain case statement" assert "case" in body.lower(), "Function body should contain case statement"
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_bash_utils_functions(self, language_server: SolidLanguageServer) -> None: def test_bash_utils_functions(self, language_server: SolidLanguageServer) -> None:
"""Test function detection in utils.sh file.""" """Test function detection in utils.sh file."""
# Test with utils.sh as well # Test with utils.sh as well
@@ -81,7 +81,7 @@ class TestBashLanguageServerBasics:
assert len(utils_function_symbols) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_function_symbols)}" assert len(utils_function_symbols) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_function_symbols)}"
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_bash_function_syntax_patterns(self, language_server: SolidLanguageServer) -> None: def test_bash_function_syntax_patterns(self, language_server: SolidLanguageServer) -> None:
"""Test that LSP detects different bash function syntax patterns correctly.""" """Test that LSP detects different bash function syntax patterns correctly."""
# Test main.sh (has both 'function' keyword and traditional syntax) # Test main.sh (has both 'function' keyword and traditional syntax)
@@ -121,7 +121,7 @@ class TestBashLanguageServerBasics:
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.sh, found {len(main_functions)}" 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)}" 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) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.bash @pytest.mark.bash
class TestBashDiagnostics: class TestBashDiagnostics:
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BASH], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+27 -25
View File
@@ -5,32 +5,34 @@ from unittest import mock
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
pytestmark = pytest.mark.skipif(not language_tests_enabled(Language.BSL), reason="BSL tests are disabled (niche, slow and flaky)") pytestmark = pytest.mark.skipif(
not language_server_tests_enabled(LanguageServerId.BSL), reason="BSL tests are disabled (niche, slow and flaky)"
)
@pytest.mark.bsl @pytest.mark.bsl
class TestBSLLanguageServer: class TestBSLLanguageServer:
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.BSL], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Language server starts and attaches to the test repository.""" """Language server starts and attaches to the test repository."""
assert language_server.is_running() assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve() assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "ВывестиСообщение"), "ВывестиСообщение not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "ВывестиСообщение"), "ВывестиСообщение not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "ПолучитьПриветствие"), "ПолучитьПриветствие not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "ПолучитьПриветствие"), "ПолучитьПриветствие not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Инициализировать"), "Инициализировать not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Инициализировать"), "Инициализировать not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
doc_symbols = language_server.request_document_symbols("CommonModule.bsl") doc_symbols = language_server.request_document_symbols("CommonModule.bsl")
all_symbols, _ = doc_symbols.get_all_symbols_and_roots() all_symbols, _ = doc_symbols.get_all_symbols_and_roots()
@@ -39,7 +41,7 @@ class TestBSLLanguageServer:
assert "ПолучитьПриветствие" in names, f"ПолучитьПриветствие not found in CommonModule.bsl symbols. Found: {names}" assert "ПолучитьПриветствие" in names, f"ПолучитьПриветствие not found in CommonModule.bsl symbols. Found: {names}"
assert "ВызватьПриветствие" in names, f"ВызватьПриветствие not found in CommonModule.bsl symbols. Found: {names}" assert "ВызватьПриветствие" in names, f"ВызватьПриветствие not found in CommonModule.bsl symbols. Found: {names}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_full_symbol_tree_within_file(self, language_server: SolidLanguageServer) -> None: def test_full_symbol_tree_within_file(self, language_server: SolidLanguageServer) -> None:
"""Scoping the full-tree request to a single file returns that file's symbols.""" """Scoping the full-tree request to a single file returns that file's symbols."""
symbols = language_server.request_full_symbol_tree(within_relative_path="ObjectModule.bsl") symbols = language_server.request_full_symbol_tree(within_relative_path="ObjectModule.bsl")
@@ -50,7 +52,7 @@ class TestBSLLanguageServer:
"ПолучитьСостояние not found in ObjectModule.bsl symbol tree" "ПолучитьСостояние not found in ObjectModule.bsl symbol tree"
) )
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None: def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
# CommonModule.bsl (0-indexed): # CommonModule.bsl (0-indexed):
# line 2: Процедура ВывестиСообщение(Текст) Экспорт <- declaration (name starts at col 10) # line 2: Процедура ВывестиСообщение(Текст) Экспорт <- declaration (name starts at col 10)
@@ -66,7 +68,7 @@ class TestBSLLanguageServer:
matching_lines = [ref["range"]["start"]["line"] for ref in refs if "CommonModule.bsl" in ref.get("relativePath", "")] matching_lines = [ref["range"]["start"]["line"] for ref in refs if "CommonModule.bsl" in ref.get("relativePath", "")]
assert 12 in matching_lines, f"Expected a reference on line 12 (0-indexed), got lines: {matching_lines}" assert 12 in matching_lines, f"Expected a reference on line 12 (0-indexed), got lines: {matching_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_find_references_to_function_within_file(self, language_server: SolidLanguageServer) -> None: def test_find_references_to_function_within_file(self, language_server: SolidLanguageServer) -> None:
# CommonModule.bsl (0-indexed): # CommonModule.bsl (0-indexed):
# line 6: Функция ПолучитьПриветствие(Имя) Экспорт <- declaration (name starts at col 8) # line 6: Функция ПолучитьПриветствие(Имя) Экспорт <- declaration (name starts at col 8)
@@ -88,7 +90,7 @@ class TestBSLLanguageServer:
_CROSS_REF_MODULE1 = os.path.join("src", "CommonModules", "ОбщийМодуль1", "Ext", "Module.bsl") _CROSS_REF_MODULE1 = os.path.join("src", "CommonModules", "ОбщийМодуль1", "Ext", "Module.bsl")
_CROSS_REF_MODULE2 = os.path.join("src", "CommonModules", "ОбщийМодуль2", "Ext", "Module.bsl") _CROSS_REF_MODULE2 = os.path.join("src", "CommonModules", "ОбщийМодуль2", "Ext", "Module.bsl")
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None: def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
"""``request_references`` from the declaration must include the cross-module call-site.""" """``request_references`` from the declaration must include the cross-module call-site."""
# ОбщийМодуль1 / Ext / Module.bsl (0-indexed): # ОбщийМодуль1 / Ext / Module.bsl (0-indexed):
@@ -104,7 +106,7 @@ class TestBSLLanguageServer:
call_site_lines = [ref["range"]["start"]["line"] for ref in refs if "ОбщийМодуль2" in ref.get("relativePath", "")] call_site_lines = [ref["range"]["start"]["line"] for ref in refs if "ОбщийМодуль2" in ref.get("relativePath", "")]
assert 3 in call_site_lines, f"Expected a reference at ОбщийМодуль2/Module.bsl line 3, got lines: {call_site_lines}" assert 3 in call_site_lines, f"Expected a reference at ОбщийМодуль2/Module.bsl line 3, got lines: {call_site_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_find_definition_across_files(self, language_server: SolidLanguageServer) -> None: def test_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
"""``request_definition`` from a cross-module call-site must resolve to the other module.""" """``request_definition`` from a cross-module call-site must resolve to the other module."""
# cursor on "ВывестиСообщение" inside # cursor on "ВывестиСообщение" inside
@@ -116,7 +118,7 @@ class TestBSLLanguageServer:
target_lines = [d["range"]["start"]["line"] for d in definitions if "ОбщийМодуль1" in d.get("relativePath", "")] target_lines = [d["range"]["start"]["line"] for d in definitions if "ОбщийМодуль1" in d.get("relativePath", "")]
assert 2 in target_lines, f"Expected the definition to point at ОбщийМодуль1/Module.bsl line 2, got: {target_lines}" assert 2 in target_lines, f"Expected the definition to point at ОбщийМодуль1/Module.bsl line 2, got: {target_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None: def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed = [s for s in all_symbols if has_malformed_name(s)] malformed = [s for s in all_symbols if has_malformed_name(s)]
@@ -133,15 +135,15 @@ class TestBSLLanguageServer:
def test_bsl_filename_matcher() -> None: def test_bsl_filename_matcher() -> None:
matcher = Language.BSL.get_source_fn_matcher() matcher = LanguageServerId.BSL.get_source_fn_matcher()
assert matcher.is_relevant_filename("module.bsl") assert matcher.is_relevant_filename("module.bsl")
assert matcher.is_relevant_filename("script.os") assert matcher.is_relevant_filename("script.os")
assert not matcher.is_relevant_filename("module.py") assert not matcher.is_relevant_filename("module.py")
def test_bsl_enum_registration() -> None: def test_bsl_enum_registration() -> None:
assert Language.BSL.value == "bsl" assert LanguageServerId.BSL.value == "bsl"
assert Language.BSL.get_ls_class().__name__ == "BSLLanguageServer" assert LanguageServerId.BSL.get_ls_class().__name__ == "BSLLanguageServer"
def test_bsl_dependency_provider_default_version() -> None: def test_bsl_dependency_provider_default_version() -> None:
@@ -153,7 +155,7 @@ def test_bsl_dependency_provider_default_version() -> None:
settings = SolidLSPSettings() settings = SolidLSPSettings()
provider = BSLLanguageServer.DependencyProvider( provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL), settings.get_ls_specific_settings(LanguageServerId.BSL),
"/tmp/ls_resources", "/tmp/ls_resources",
) )
@@ -174,9 +176,9 @@ def test_bsl_dependency_provider_custom_version_no_sha() -> None:
from solidlsp.language_servers.common import RuntimeDependencyCollection from solidlsp.language_servers.common import RuntimeDependencyCollection
settings = SolidLSPSettings() settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"bsl_ls_version": "0.28.0"} settings.ls_specific_settings[LanguageServerId.BSL] = {"bsl_ls_version": "0.28.0"}
provider = BSLLanguageServer.DependencyProvider( provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL), settings.get_ls_specific_settings(LanguageServerId.BSL),
"/tmp/ls_resources", "/tmp/ls_resources",
) )
@@ -214,9 +216,9 @@ def test_bsl_launch_command_uses_ls_path_without_download() -> None:
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
settings = SolidLSPSettings() settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"} settings.ls_specific_settings[LanguageServerId.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider( provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL), settings.get_ls_specific_settings(LanguageServerId.BSL),
"/tmp/ls_resources", "/tmp/ls_resources",
) )
@@ -241,9 +243,9 @@ def test_bsl_launch_command_requires_java() -> None:
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
settings = SolidLSPSettings() settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"} settings.ls_specific_settings[LanguageServerId.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider( provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL), settings.get_ls_specific_settings(LanguageServerId.BSL),
"/tmp/ls_resources", "/tmp/ls_resources",
) )
@@ -258,9 +260,9 @@ def test_bsl_launch_command_rejects_old_java() -> None:
from solidlsp.language_servers.bsl_language_server import BSL_LS_MIN_JAVA_VERSION, BSLLanguageServer from solidlsp.language_servers.bsl_language_server import BSL_LS_MIN_JAVA_VERSION, BSLLanguageServer
settings = SolidLSPSettings() settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"} settings.ls_specific_settings[LanguageServerId.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider( provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL), settings.get_ls_specific_settings(LanguageServerId.BSL),
"/tmp/ls_resources", "/tmp/ls_resources",
) )
+4 -4
View File
@@ -1,15 +1,15 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.bsl @pytest.mark.bsl
@pytest.mark.skipif(not language_tests_enabled(Language.BSL), reason="BSL tests are disabled") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.BSL), reason="BSL tests are disabled")
class TestBSLDiagnostics: class TestBSLDiagnostics:
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.BSL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
"""bsl-language-server must flag the unterminated string literal in the fixture.""" """bsl-language-server must flag the unterminated string literal in the fixture."""
assert_file_diagnostics( assert_file_diagnostics(
+17 -17
View File
@@ -2,18 +2,18 @@ import pytest
from serena.project import Project from serena.project import Project
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from . import CORE_PATH, UTILS_PATH from . import CORE_PATH, UTILS_PATH
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.CLOJURE), reason="Clojure tests are disabled")
@pytest.mark.clojure @pytest.mark.clojure
class TestLanguageServerBasics: class TestLanguageServerBasics:
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_basic_definition(self, language_server: SolidLanguageServer): def test_basic_definition(self, language_server: SolidLanguageServer):
""" """
Test finding definition of 'greet' function call in core.clj Test finding definition of 'greet' function call in core.clj
@@ -27,7 +27,7 @@ class TestLanguageServerBasics:
assert definition["relativePath"] == CORE_PATH assert definition["relativePath"] == CORE_PATH
assert definition["range"]["start"]["line"] == 2, "Should find the definition of greet function at line 2" assert definition["range"]["start"]["line"] == 2, "Should find the definition of greet function at line 2"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_cross_file_references(self, language_server: SolidLanguageServer): def test_cross_file_references(self, language_server: SolidLanguageServer):
""" """
Test finding references to 'multiply' function from core.clj Test finding references to 'multiply' function from core.clj
@@ -42,7 +42,7 @@ class TestLanguageServerBasics:
) )
assert usage_found, "Should find multiply usage in utils.clj" assert usage_found, "Should find multiply usage in utils.clj"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_completions(self, language_server: SolidLanguageServer): def test_completions(self, language_server: SolidLanguageServer):
with language_server.open_file(UTILS_PATH): with language_server.open_file(UTILS_PATH):
# After "core/" in calculate-area # After "core/" in calculate-area
@@ -53,7 +53,7 @@ class TestLanguageServerBasics:
completion_texts = [item["completionText"] for item in result] completion_texts = [item["completionText"] for item in result]
assert any("multiply" in text for text in completion_texts), "Should find 'multiply' function in completions after 'core/'" assert any("multiply" in text for text in completion_texts), "Should find 'multiply' function in completions after 'core/'"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_document_symbols(self, language_server: SolidLanguageServer): def test_document_symbols(self, language_server: SolidLanguageServer):
symbols, _ = language_server.request_document_symbols(CORE_PATH).get_all_symbols_and_roots() symbols, _ = language_server.request_document_symbols(CORE_PATH).get_all_symbols_and_roots()
@@ -66,7 +66,7 @@ class TestLanguageServerBasics:
for func_name in expected_functions: for func_name in expected_functions:
assert func_name in symbol_names, f"Should find {func_name} function in symbols" assert func_name in symbol_names, f"Should find {func_name} function in symbols"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_hover(self, language_server: SolidLanguageServer): def test_hover(self, language_server: SolidLanguageServer):
"""Test hover on greet function""" """Test hover on greet function"""
result = language_server.request_hover(CORE_PATH, 2, 7) result = language_server.request_hover(CORE_PATH, 2, 7)
@@ -82,7 +82,7 @@ class TestLanguageServerBasics:
else: else:
assert False, f"Unexpected contents format: {type(contents)}" assert False, f"Unexpected contents format: {type(contents)}"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_workspace_symbols(self, language_server: SolidLanguageServer): def test_workspace_symbols(self, language_server: SolidLanguageServer):
# Search for functions containing "add" # Search for functions containing "add"
result = language_server.request_workspace_symbol("add") result = language_server.request_workspace_symbol("add")
@@ -93,7 +93,7 @@ class TestLanguageServerBasics:
symbol_names = [symbol["name"] for symbol in result] symbol_names = [symbol["name"] for symbol in result]
assert any("add" in name.lower() for name in symbol_names), f"Should find 'add' function in symbols: {symbol_names}" assert any("add" in name.lower() for name in symbol_names), f"Should find 'add' function in symbols: {symbol_names}"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_namespace_functions(self, language_server: SolidLanguageServer): def test_namespace_functions(self, language_server: SolidLanguageServer):
"""Test definition lookup for core/greet usage in utils.clj""" """Test definition lookup for core/greet usage in utils.clj"""
# Position of 'greet' in core/greet call # Position of 'greet' in core/greet call
@@ -105,7 +105,7 @@ class TestLanguageServerBasics:
definition = result[0] definition = result[0]
assert definition["relativePath"] == CORE_PATH, "Should find the definition of greet in core.clj" assert definition["relativePath"] == CORE_PATH, "Should find the definition of greet in core.clj"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_request_references_with_content(self, language_server: SolidLanguageServer): def test_request_references_with_content(self, language_server: SolidLanguageServer):
"""Test references to multiply function with content""" """Test references to multiply function with content"""
references = language_server.request_references(CORE_PATH, 12, 6) references = language_server.request_references(CORE_PATH, 12, 6)
@@ -130,7 +130,7 @@ class TestLanguageServerBasics:
utils_content = utils_refs[0].to_display_string() utils_content = utils_refs[0].to_display_string()
assert "calculate-area" in utils_content assert "calculate-area" in utils_content
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_request_full_symbol_tree(self, language_server: SolidLanguageServer): def test_request_full_symbol_tree(self, language_server: SolidLanguageServer):
"""Test retrieving the full symbol tree for project overview """Test retrieving the full symbol tree for project overview
We just check that we find some expected symbols. We just check that we find some expected symbols.
@@ -172,7 +172,7 @@ class TestLanguageServerBasics:
f"Symbol tree structure:\n{traverse_symbols(result)}" f"Symbol tree structure:\n{traverse_symbols(result)}"
) )
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_request_referencing_symbols(self, language_server: SolidLanguageServer): def test_request_referencing_symbols(self, language_server: SolidLanguageServer):
"""Test finding symbols that reference a given symbol """Test finding symbols that reference a given symbol
Finds references to the 'multiply' function. Finds references to the 'multiply' function.
@@ -188,10 +188,10 @@ class TestLanguageServerBasics:
assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}" 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.skipif(not language_server_tests_enabled(LanguageServerId.CLOJURE), reason="Clojure tests are disabled")
@pytest.mark.clojure @pytest.mark.clojure
class TestProjectBasics: class TestProjectBasics:
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("project", [LanguageServerId.CLOJURE], indirect=True)
def test_retrieve_content_around_line(self, project: Project): def test_retrieve_content_around_line(self, project: Project):
"""Test retrieving content around specific lines""" """Test retrieving content around specific lines"""
# Test retrieving content around the greet function definition (line 2) # Test retrieving content around the greet function definition (line 2)
@@ -209,7 +209,7 @@ class TestProjectBasics:
content_str = result.to_display_string() content_str = result.to_display_string()
assert "multiply" in content_str, "Should contain multiply function" assert "multiply" in content_str, "Should contain multiply function"
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("project", [LanguageServerId.CLOJURE], indirect=True)
def test_search_files_for_pattern(self, project: Project) -> None: def test_search_files_for_pattern(self, project: Project) -> None:
result = project.search_project_files_for_pattern("defn.*greet") result = project.search_project_files_for_pattern("defn.*greet")
@@ -225,7 +225,7 @@ class TestProjectBasics:
utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path] 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" assert len(utils_matches) > 0, "Should find require statement in utils.clj"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -3,13 +3,13 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.clojure @pytest.mark.clojure
class TestClojureDiagnostics: class TestClojureDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
@@ -19,8 +19,8 @@ import pytest
from serena.util.text_utils import find_text_coordinates from serena.util.text_utils import find_text_coordinates
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import read_repo_file from test.solidlsp.conftest import read_repo_file
from . import CORE_PATH, TEST_APP_PATH from . import CORE_PATH, TEST_APP_PATH
@@ -29,7 +29,7 @@ EXTRA_PATH = str(TEST_APP_PATH / "extra.clj")
SUBMODULE_CONSUMER_PATH = str(Path("sub_module") / "src" / "sub_module_app" / "consumer.clj") SUBMODULE_CONSUMER_PATH = str(Path("sub_module") / "src" / "sub_module_app" / "consumer.clj")
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.CLOJURE), reason="Clojure tests are disabled")
@pytest.mark.clojure @pytest.mark.clojure
class TestClojureProjectIndexing: class TestClojureProjectIndexing:
"""Covers the "indexing leaks through results" bug for clojure-lsp. """Covers the "indexing leaks through results" bug for clojure-lsp.
@@ -41,7 +41,7 @@ class TestClojureProjectIndexing:
indexing of those calls. indexing of those calls.
""" """
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_request_references_includes_unopened_file(self, language_server: SolidLanguageServer) -> None: def test_request_references_includes_unopened_file(self, language_server: SolidLanguageServer) -> None:
# locate the definition of `multiply` in core.clj without hardcoding coords # locate the definition of `multiply` in core.clj without hardcoding coords
core_content = read_repo_file(language_server, CORE_PATH) core_content = read_repo_file(language_server, CORE_PATH)
@@ -65,7 +65,7 @@ class TestClojureProjectIndexing:
f"Expected at least 2 references in extra.clj (double-product and triple-product), got {len(extra_refs)}: {extra_refs}" f"Expected at least 2 references in extra.clj (double-product and triple-product), got {len(extra_refs)}: {extra_refs}"
) )
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_request_references_includes_unopened_sibling_module_file(self, language_server: SolidLanguageServer) -> None: def test_request_references_includes_unopened_sibling_module_file(self, language_server: SolidLanguageServer) -> None:
"""Mirrors the real-world penpot bug repro: ``multiply`` is defined in one """Mirrors the real-world penpot bug repro: ``multiply`` is defined in one
module (root ``src/``) and consumed from a sibling module that has its module (root ``src/``) and consumed from a sibling module that has its
@@ -92,7 +92,7 @@ class TestClojureProjectIndexing:
"get_symbols_overview, so reference search returns silently incomplete results." "get_symbols_overview, so reference search returns silently incomplete results."
) )
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CLOJURE], indirect=True)
def test_request_referencing_symbols_includes_unopened_file(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_includes_unopened_file(self, language_server: SolidLanguageServer) -> None:
core_content = read_repo_file(language_server, CORE_PATH) core_content = read_repo_file(language_server, CORE_PATH)
coords = find_text_coordinates(core_content, r"\(defn (multiply)\b", require_unique=True) coords = find_text_coordinates(core_content, r"\(defn (multiply)\b", require_unique=True)
+5 -5
View File
@@ -1,14 +1,14 @@
from pathlib import Path from pathlib import Path
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
PYTHON_BACKEND_LANGUAGES = [ PYTHON_BACKEND_LANGUAGES = [
Language.PYTHON, LanguageServerId.PYTHON,
Language.PYTHON_TY, LanguageServerId.PYTHON_TY,
Language.PYTHON_PYREFLY, LanguageServerId.PYTHON_PYREFLY,
Language.PYTHON_BASEDPYRIGHT, LanguageServerId.PYTHON_BASEDPYRIGHT,
] ]
+2 -2
View File
@@ -2,7 +2,7 @@ import os
import pytest import pytest
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import start_ls_context from test.conftest import start_ls_context
@@ -16,7 +16,7 @@ class TestCCLSLanguages:
], ],
) )
def test_get_document_symbols(self, lang: str, unit: str, names: set[str]) -> None: def test_get_document_symbols(self, lang: str, unit: str, names: set[str]) -> None:
with start_ls_context(Language.CPP) as ccls: with start_ls_context(LanguageServerId.CPP) as ccls:
path = os.path.join(lang, unit) path = os.path.join(lang, unit)
symbols = ccls.request_document_symbols(path).get_all_symbols_and_roots() symbols = ccls.request_document_symbols(path).get_all_symbols_and_roots()
symbols = symbols[0] if symbols and isinstance(symbols[0], list) else symbols symbols = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
+2 -2
View File
@@ -2,7 +2,7 @@ import os
import pytest import pytest
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import start_ls_context from test.conftest import start_ls_context
@@ -22,7 +22,7 @@ class TestClangdLanguages:
], ],
) )
def test_get_document_symbols(self, lang: str, unit: str, names: set[str]) -> None: def test_get_document_symbols(self, lang: str, unit: str, names: set[str]) -> None:
with start_ls_context(Language.CPP) as clangd: with start_ls_context(LanguageServerId.CPP) as clangd:
path = os.path.join(lang, unit) path = os.path.join(lang, unit)
symbols = clangd.request_document_symbols(path).get_all_symbols_and_roots() symbols = clangd.request_document_symbols(path).get_all_symbols_and_roots()
symbols = symbols[0] if symbols and isinstance(symbols[0], list) else symbols symbols = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
+12 -12
View File
@@ -14,18 +14,18 @@ from pathlib import Path
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import get_repo_path, language_tests_enabled, start_ls_context from test.conftest import get_repo_path, language_server_tests_enabled, start_ls_context
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
_cpp_servers: list[Language] = [Language.CPP] _cpp_servers: list[LanguageServerId] = [LanguageServerId.CPP]
if language_tests_enabled(Language.CPP_CCLS): if language_server_tests_enabled(LanguageServerId.CPP_CCLS):
_cpp_servers.append(Language.CPP_CCLS) _cpp_servers.append(LanguageServerId.CPP_CCLS)
@pytest.mark.parametrize("language", [Language.CPP, Language.CPP_CCLS]) @pytest.mark.parametrize("language", [LanguageServerId.CPP, LanguageServerId.CPP_CCLS])
def test_source_fn_matcher_includes_ino(language: Language) -> None: def test_source_fn_matcher_includes_ino(language: LanguageServerId) -> None:
"""Arduino .ino sketches are C++ and must route to the C++ language server. """Arduino .ino sketches are C++ and must route to the C++ language server.
This is a pure matcher check; it needs no running language server. This is a pure matcher check; it needs no running language server.
@@ -172,7 +172,7 @@ int use_add() {
@pytest.mark.cpp @pytest.mark.cpp
class TestCppDocumentSymbolCache: class TestCppDocumentSymbolCache:
def _copy_cpp_fixture(self, tmp_path: Path) -> Path: def _copy_cpp_fixture(self, tmp_path: Path) -> Path:
fixture_path = get_repo_path(Language.CPP) fixture_path = get_repo_path(LanguageServerId.CPP)
target_path = tmp_path / "test_repo" target_path = tmp_path / "test_repo"
shutil.copytree(fixture_path, target_path) shutil.copytree(fixture_path, target_path)
return target_path return target_path
@@ -180,7 +180,7 @@ class TestCppDocumentSymbolCache:
def test_cache_invalidates_when_clangd_context_changes(self, tmp_path: Path) -> None: def test_cache_invalidates_when_clangd_context_changes(self, tmp_path: Path) -> None:
repo_path = self._copy_cpp_fixture(tmp_path) repo_path = self._copy_cpp_fixture(tmp_path)
ls_settings_alt = { ls_settings_alt = {
Language.CPP: { LanguageServerId.CPP: {
"compile_commands_dir": ".serena-alt", "compile_commands_dir": ".serena-alt",
} }
} }
@@ -201,7 +201,7 @@ class TestCppDocumentSymbolCache:
assert ls._raw_document_symbols_cache_is_modified assert ls._raw_document_symbols_cache_is_modified
assert ls._document_symbols_cache_is_modified assert ls._document_symbols_cache_is_modified
with start_ls_context(Language.CPP, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default: with start_ls_context(LanguageServerId.CPP, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default:
_ = ls_default.request_document_symbols(main_cpp) _ = ls_default.request_document_symbols(main_cpp)
default_raw_cache_version = ls_default._raw_document_symbols_cache_version() default_raw_cache_version = ls_default._raw_document_symbols_cache_version()
@@ -212,7 +212,7 @@ class TestCppDocumentSymbolCache:
cache_files = [p for p in cache_dir.rglob("*") if p.is_file()] cache_files = [p for p in cache_dir.rglob("*") if p.is_file()]
assert cache_files, f"Expected SolidLSP to create cache artifacts under {cache_dir}" assert cache_files, f"Expected SolidLSP to create cache artifacts under {cache_dir}"
with start_ls_context(Language.CPP, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default_again: with start_ls_context(LanguageServerId.CPP, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default_again:
assert ls_default_again.cache_dir == cache_dir assert ls_default_again.cache_dir == cache_dir
_assert_caches_loaded_and_clean(ls_default_again) _assert_caches_loaded_and_clean(ls_default_again)
_ = ls_default_again.request_document_symbols(main_cpp) _ = ls_default_again.request_document_symbols(main_cpp)
@@ -220,7 +220,7 @@ class TestCppDocumentSymbolCache:
assert not ls_default_again._document_symbols_cache_is_modified assert not ls_default_again._document_symbols_cache_is_modified
with start_ls_context( with start_ls_context(
Language.CPP, LanguageServerId.CPP,
repo_path=str(repo_path), repo_path=str(repo_path),
ls_specific_settings=ls_settings_alt, ls_specific_settings=ls_settings_alt,
solidlsp_dir=tmp_path, solidlsp_dir=tmp_path,
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.cpp @pytest.mark.cpp
class TestCppDiagnostics: class TestCppDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CPP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CPP], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+3 -3
View File
@@ -23,12 +23,12 @@ from collections.abc import Iterator
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import find_identifier_position, get_repo_path, start_ls_context from test.conftest import find_identifier_position, get_repo_path, start_ls_context
from test.solidlsp.conftest import document_symbol_names, find_document_symbol from test.solidlsp.conftest import document_symbol_names, find_document_symbol
UE_REPO_PATH = get_repo_path(Language.CPP).parent / "ue_test_repo" UE_REPO_PATH = get_repo_path(LanguageServerId.CPP).parent / "ue_test_repo"
ABILITY_COMPONENT_H = os.path.join("Source", "TestGame", "AbilityComponent.h") ABILITY_COMPONENT_H = os.path.join("Source", "TestGame", "AbilityComponent.h")
ABILITY_ACTOR_H = os.path.join("Source", "TestGame", "AbilityActor.h") ABILITY_ACTOR_H = os.path.join("Source", "TestGame", "AbilityActor.h")
ABILITY_ACTOR_CPP = os.path.join("Source", "TestGame", "AbilityActor.cpp") ABILITY_ACTOR_CPP = os.path.join("Source", "TestGame", "AbilityActor.cpp")
@@ -37,7 +37,7 @@ ABILITY_ACTOR_CPP = os.path.join("Source", "TestGame", "AbilityActor.cpp")
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def language_server() -> Iterator[SolidLanguageServer]: def language_server() -> Iterator[SolidLanguageServer]:
"""Clangd over ue_test_repo; overrides the shared fixture for this module.""" """Clangd over ue_test_repo; overrides the shared fixture for this module."""
with start_ls_context(Language.CPP, repo_path=str(UE_REPO_PATH)) as ls: with start_ls_context(LanguageServerId.CPP, repo_path=str(UE_REPO_PATH)) as ls:
yield ls yield ls
+11 -9
View File
@@ -14,25 +14,27 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
pytestmark = [ pytestmark = [
pytest.mark.crystal, pytest.mark.crystal,
pytest.mark.skipif(not language_tests_enabled(Language.CRYSTAL), reason="Crystal tests are disabled (crystalline not available)"), pytest.mark.skipif(
not language_server_tests_enabled(LanguageServerId.CRYSTAL), reason="Crystal tests are disabled (crystalline not available)"
),
] ]
class TestCrystalDocumentSymbols: class TestCrystalDocumentSymbols:
"""Test document symbol retrieval, which works reliably in Crystalline.""" """Test document symbol retrieval, which works reliably in Crystalline."""
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer) -> None:
"""Test that the language server starts successfully.""" """Test that the language server starts successfully."""
assert language_server.is_running() assert language_server.is_running()
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_document_symbols_main(self, language_server: SolidLanguageServer) -> None: def test_document_symbols_main(self, language_server: SolidLanguageServer) -> None:
"""Test that document symbols are returned for the main file.""" """Test that document symbols are returned for the main file."""
file_path = os.path.join("src", "main.cr") file_path = os.path.join("src", "main.cr")
@@ -43,7 +45,7 @@ class TestCrystalDocumentSymbols:
assert "Calculator" in symbol_names, f"Calculator not found in symbols. Found: {symbol_names}" assert "Calculator" in symbol_names, f"Calculator not found in symbols. Found: {symbol_names}"
assert "User" in symbol_names, f"User not found in symbols. Found: {symbol_names}" assert "User" in symbol_names, f"User not found in symbols. Found: {symbol_names}"
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_document_symbols_utils(self, language_server: SolidLanguageServer) -> None: def test_document_symbols_utils(self, language_server: SolidLanguageServer) -> None:
"""Test that document symbols are returned for the utils file.""" """Test that document symbols are returned for the utils file."""
file_path = os.path.join("src", "utils.cr") file_path = os.path.join("src", "utils.cr")
@@ -53,7 +55,7 @@ class TestCrystalDocumentSymbols:
symbol_names = [s.get("name") for s in all_symbols if s.get("name")] symbol_names = [s.get("name") for s in all_symbols if s.get("name")]
assert "Utils" in symbol_names, f"Utils not found in symbols. Found: {symbol_names}" assert "Utils" in symbol_names, f"Utils not found in symbols. Found: {symbol_names}"
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test that the full symbol tree contains expected symbols.""" """Test that the full symbol tree contains expected symbols."""
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
@@ -63,7 +65,7 @@ class TestCrystalDocumentSymbols:
assert SymbolUtils.symbol_tree_contains_name(symbols, "User"), "User not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "User"), "User not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None: def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
"""Test that symbol names do not contain unexpected formatting characters.""" """Test that symbol names do not contain unexpected formatting characters."""
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
@@ -86,7 +88,7 @@ class TestCrystalDefinition:
module-scoped ``language_server`` fixture ensures we get a fresh server. module-scoped ``language_server`` fixture ensures we get a fresh server.
""" """
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_goto_definition_within_file(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_within_file(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition for a symbol defined within the same file.""" """Test goto_definition for a symbol defined within the same file."""
file_path = os.path.join("src", "main.cr") file_path = os.path.join("src", "main.cr")
@@ -1,18 +1,20 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
pytestmark = [ pytestmark = [
pytest.mark.crystal, pytest.mark.crystal,
pytest.mark.skipif(not language_tests_enabled(Language.CRYSTAL), reason="Crystal tests are disabled (crystalline not available)"), pytest.mark.skipif(
not language_server_tests_enabled(LanguageServerId.CRYSTAL), reason="Crystal tests are disabled (crystalline not available)"
),
] ]
class TestCrystalDiagnostics: class TestCrystalDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CRYSTAL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+14 -14
View File
@@ -10,16 +10,16 @@ from solidlsp.language_servers.csharp_language_server import (
breadth_first_file_scan, breadth_first_file_scan,
find_solution_or_project_file, find_solution_or_project_file,
) )
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import find_identifier_position, get_repo_path, language_has_verified_implementation_support from test.conftest import find_identifier_position, get_repo_path, ls_has_verified_implementation_support
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.csharp @pytest.mark.csharp
class TestCSharpLanguageServer: class TestCSharpLanguageServer:
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test finding symbols in the full symbol tree.""" """Test finding symbols in the full symbol tree."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -27,7 +27,7 @@ class TestCSharpLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator class not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Add"), "Add method not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Add"), "Add method not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_get_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_get_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test getting document symbols from a C# file.""" """Test getting document symbols from a C# file."""
file_path = os.path.join("Program.cs") file_path = os.path.join("Program.cs")
@@ -45,7 +45,7 @@ class TestCSharpLanguageServer:
assert "Program" in class_names assert "Program" in class_names
assert "Calculator" in class_names assert "Calculator" in class_names
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test finding references using symbol selection range.""" """Test finding references using symbol selection range."""
file_path = os.path.join("Program.cs") file_path = os.path.join("Program.cs")
@@ -65,7 +65,7 @@ class TestCSharpLanguageServer:
"Program.cs should reference Add method (tried all positions in selectionRange)" "Program.cs should reference Add method (tried all positions in selectionRange)"
) )
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_nested_namespace_symbols(self, language_server: SolidLanguageServer) -> None: def test_nested_namespace_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test getting symbols from nested namespace.""" """Test getting symbols from nested namespace."""
file_path = os.path.join("Models", "Person.cs") file_path = os.path.join("Models", "Person.cs")
@@ -89,7 +89,7 @@ class TestCSharpLanguageServer:
assert "ToString" in symbol_names, "ToString method not found" assert "ToString" in symbol_names, "ToString method not found"
assert "IsAdult" in symbol_names, "IsAdult method not found" assert "IsAdult" in symbol_names, "IsAdult method not found"
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_find_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None:
"""Test finding references to Calculator.Subtract method across files.""" """Test finding references to Calculator.Subtract method across files."""
# First, find the Subtract method in Program.cs # First, find the Subtract method in Program.cs
@@ -128,7 +128,7 @@ class TestCSharpLanguageServer:
refs_second_call = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1) refs_second_call = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1)
assert refs_second_call == refs, "Second call to request_references should return the same results" assert refs_second_call == refs, "Second call to request_references should return the same results"
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_hover_includes_type_information(self, language_server: SolidLanguageServer) -> None: def test_hover_includes_type_information(self, language_server: SolidLanguageServer) -> None:
"""Test that hover information is available and includes type information.""" """Test that hover information is available and includes type information."""
file_path = os.path.join("Models", "Person.cs") file_path = os.path.join("Models", "Person.cs")
@@ -172,11 +172,11 @@ class TestCSharpLanguageServer:
assert "bool" in method_hover_text, f"Hover should include 'bool' return type, got: {method_hover_text}" assert "bool" in method_hover_text, f"Hover should include 'bool' return type, got: {method_hover_text}"
assert "IsAdult" in method_hover_text, f"Hover should include 'IsAdult' method name, got: {method_hover_text}" assert "IsAdult" in method_hover_text, f"Hover should include 'IsAdult' method name, got: {method_hover_text}"
if language_has_verified_implementation_support(Language.CSHARP): if ls_has_verified_implementation_support(LanguageServerId.CSHARP):
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_find_implementations(self, language_server: SolidLanguageServer) -> None: def test_find_implementations(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.CSHARP) repo_path = get_repo_path(LanguageServerId.CSHARP)
pos = find_identifier_position(repo_path / "Services" / "IGreeter.cs", "FormatGreeting") pos = find_identifier_position(repo_path / "Services" / "IGreeter.cs", "FormatGreeting")
assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture" assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture"
@@ -186,9 +186,9 @@ class TestCSharpLanguageServer:
f"Expected ConsoleGreeter.FormatGreeting in implementations, got: {implementations}" f"Expected ConsoleGreeter.FormatGreeting in implementations, got: {implementations}"
) )
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.CSHARP) repo_path = get_repo_path(LanguageServerId.CSHARP)
pos = find_identifier_position(repo_path / "Services" / "IGreeter.cs", "FormatGreeting") pos = find_identifier_position(repo_path / "Services" / "IGreeter.cs", "FormatGreeting")
assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture" assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture"
@@ -327,7 +327,7 @@ class TestCSharpSolutionProjectOpening:
# Verify the file actually exists # Verify the file actually exists
assert os.path.exists(result) assert os.path.exists(result)
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.csharp @pytest.mark.csharp
class TestCsharpDiagnostics: class TestCsharpDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CSHARP], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+14 -14
View File
@@ -5,7 +5,7 @@ from pathlib import Path
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@@ -27,14 +27,14 @@ class TestCueLanguageServer:
``cue lsp v0.16.1`` responses against this repository. ``cue lsp v0.16.1`` responses against this repository.
""" """
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.CUE], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""The server starts and reports the expected repository root.""" """The server starts and reports the expected repository root."""
assert language_server.is_running() assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve() assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
def test_document_symbols_schema(self, language_server: SolidLanguageServer) -> None: def test_document_symbols_schema(self, language_server: SolidLanguageServer) -> None:
"""``schema.cue`` exposes its top-level definitions with the expected hierarchy.""" """``schema.cue`` exposes its top-level definitions with the expected hierarchy."""
# request hierarchical document symbols for schema.cue # request hierarchical document symbols for schema.cue
@@ -54,7 +54,7 @@ class TestCueLanguageServer:
# fields must not also appear at root level (that would be the flat fallback) # fields must not also appear at root level (that would be the flat fallback)
assert "name" not in root_names, f"name should be a child of #Person, not a root. Roots: {root_names}" assert "name" not in root_names, f"name should be a child of #Person, not a root. Roots: {root_names}"
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
def test_full_symbol_tree_contains_cross_file_names(self, language_server: SolidLanguageServer) -> None: def test_full_symbol_tree_contains_cross_file_names(self, language_server: SolidLanguageServer) -> None:
"""The repository-wide symbol tree contains definitions from all three CUE files.""" """The repository-wide symbol tree contains definitions from all three CUE files."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -71,8 +71,8 @@ class TestCueLanguageServer:
): ):
assert SymbolUtils.symbol_tree_contains_name(symbols, expected), f"{expected} missing from full symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, expected), f"{expected} missing from full symbol tree"
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.CUE], indirect=True)
def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""``#Person`` used in ``main.cue`` resolves to its definition in ``schema.cue``.""" """``#Person`` used in ``main.cue`` resolves to its definition in ``schema.cue``."""
# main.cue line 3 (0-indexed): "alice: #Person & {" # main.cue line 3 (0-indexed): "alice: #Person & {"
@@ -85,8 +85,8 @@ class TestCueLanguageServer:
assert target["uri"].endswith("schema.cue") assert target["uri"].endswith("schema.cue")
assert target["range"]["start"] == {"line": 3, "character": 0} assert target["range"]["start"] == {"line": 3, "character": 0}
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.CUE], indirect=True)
def test_find_references_across_files_person(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_across_files_person(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""``#Person`` has references in ``lib.cue`` and ``main.cue`` beyond its declaration.""" """``#Person`` has references in ``lib.cue`` and ``main.cue`` beyond its declaration."""
schema_path = str(repo_path / "schema.cue") schema_path = str(repo_path / "schema.cue")
@@ -103,8 +103,8 @@ class TestCueLanguageServer:
assert ("lib.cue", 5) in ref_pairs, f"Expected lib.cue:5 reference, got {sorted(ref_pairs)}" assert ("lib.cue", 5) in ref_pairs, f"Expected lib.cue:5 reference, got {sorted(ref_pairs)}"
assert ("main.cue", 3) in ref_pairs, f"Expected main.cue:3 reference, got {sorted(ref_pairs)}" assert ("main.cue", 3) in ref_pairs, f"Expected main.cue:3 reference, got {sorted(ref_pairs)}"
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.CUE], indirect=True)
def test_find_references_within_file_build_greeting(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_within_file_build_greeting(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""``#BuildGreeting`` (defined in lib.cue) is referenced from main.cue.""" """``#BuildGreeting`` (defined in lib.cue) is referenced from main.cue."""
lib_path = str(repo_path / "lib.cue") lib_path = str(repo_path / "lib.cue")
@@ -117,8 +117,8 @@ class TestCueLanguageServer:
# use site in main.cue is line 10: "greetingForAlice: (#BuildGreeting & {for_: alice}).result" # use site in main.cue is line 10: "greetingForAlice: (#BuildGreeting & {for_: alice}).result"
assert ("main.cue", 10) in ref_pairs, f"Expected main.cue:10 reference, got {sorted(ref_pairs)}" assert ("main.cue", 10) in ref_pairs, f"Expected main.cue:10 reference, got {sorted(ref_pairs)}"
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.CUE], indirect=True)
def test_find_references_default_locale(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_default_locale(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""``defaultLocale`` (schema.cue) is used in main.cue.""" """``defaultLocale`` (schema.cue) is used in main.cue."""
schema_path = str(repo_path / "schema.cue") schema_path = str(repo_path / "schema.cue")
@@ -130,7 +130,7 @@ class TestCueLanguageServer:
# use site in main.cue is line 13: "locale: defaultLocale" # use site in main.cue is line 13: "locale: defaultLocale"
assert ("main.cue", 13) in ref_pairs, f"Expected main.cue:13 reference, got {sorted(ref_pairs)}" assert ("main.cue", 13) in ref_pairs, f"Expected main.cue:13 reference, got {sorted(ref_pairs)}"
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.CUE], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None: def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
"""CUE symbols must have bare names (no whitespace/bracket/paren/comma/colon pollution).""" """CUE symbols must have bare names (no whitespace/bracket/paren/comma/colon pollution)."""
# `.` is allowed because the synthetic directory symbol for `cue.mod/` contains a literal # `.` is allowed because the synthetic directory symbol for `cue.mod/` contains a literal
+30 -30
View File
@@ -5,7 +5,7 @@ import pytest
from serena.util.text_utils import find_text_coordinates from serena.util.text_utils import find_text_coordinates
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@@ -13,16 +13,16 @@ from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name,
@pytest.mark.dart @pytest.mark.dart
class TestDartLanguageServer: class TestDartLanguageServer:
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test that the language server starts and stops successfully.""" """Test that the language server starts and stops successfully."""
# The fixture already handles start and stop # The fixture already handles start and stop
assert language_server.is_running() assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve() assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding definition of a method within the same file.""" """Test finding definition of a method within the same file."""
# In lib/main.dart: # In lib/main.dart:
@@ -43,8 +43,8 @@ class TestDartLanguageServer:
# But language server may return different positions # But language server may return different positions
assert definition_location["range"]["start"]["line"] >= 0 assert definition_location["range"]["start"]["line"] >= 0
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding definition across different files.""" """Test finding definition across different files."""
# Test finding definition of MathHelper class which is in helper.dart # Test finding definition of MathHelper class which is in helper.dart
@@ -65,8 +65,8 @@ class TestDartLanguageServer:
assert definition_location["uri"].endswith("helper.dart") assert definition_location["uri"].endswith("helper.dart")
assert definition_location["range"]["start"]["line"] >= 0 assert definition_location["range"]["start"]["line"] >= 0
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_definition_class_method(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_class_method(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding definition of a class method.""" """Test finding definition of a class method."""
# In lib/main.dart: # In lib/main.dart:
@@ -86,8 +86,8 @@ class TestDartLanguageServer:
# Definition of power method should be around line 13 (0-indexed) # Definition of power method should be around line 13 (0-indexed)
assert 12 <= definition_location["range"]["start"]["line"] <= 16 assert 12 <= definition_location["range"]["start"]["line"] <= 16
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_within_file(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding references to a method within the same file.""" """Test finding references to a method within the same file."""
main_dart_path = str(repo_path / "lib" / "main.dart") main_dart_path = str(repo_path / "lib" / "main.dart")
@@ -105,8 +105,8 @@ class TestDartLanguageServer:
main_dart_references = [ref for ref in references if ref["uri"].endswith("main.dart")] main_dart_references = [ref for ref in references if ref["uri"].endswith("main.dart")]
assert len(main_dart_references) >= 1 assert len(main_dart_references) >= 1
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_references_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding references across different files.""" """Test finding references across different files."""
helper_dart_path = str(repo_path / "lib" / "helper.dart") helper_dart_path = str(repo_path / "lib" / "helper.dart")
@@ -121,8 +121,8 @@ class TestDartLanguageServer:
main_dart_references = [ref for ref in references if ref["uri"].endswith("main.dart")] main_dart_references = [ref for ref in references if ref["uri"].endswith("main.dart")]
assert len(main_dart_references) >= 1 assert len(main_dart_references) >= 1
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_definition_constructor(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_constructor(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding definition of a constructor call.""" """Test finding definition of a constructor call."""
main_dart_path = str(repo_path / "lib" / "main.dart") main_dart_path = str(repo_path / "lib" / "main.dart")
@@ -139,8 +139,8 @@ class TestDartLanguageServer:
# Definition of Calculator class should be around line 3 (0-indexed) # Definition of Calculator class should be around line 3 (0-indexed)
assert 3 <= definition_location["range"]["start"]["line"] <= 7 assert 3 <= definition_location["range"]["start"]["line"] <= 7
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.DART], indirect=True)
def test_find_definition_import(self, language_server: SolidLanguageServer, repo_path: Path) -> None: def test_find_definition_import(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Test finding definition through imports.""" """Test finding definition through imports."""
models_dart_path = str(repo_path / "lib" / "models.dart") models_dart_path = str(repo_path / "lib" / "models.dart")
@@ -159,7 +159,7 @@ class TestDartLanguageServer:
# This is acceptable behavior - the important thing is that it found a definition # This is acceptable behavior - the important thing is that it found a definition
assert "dart" in definition_location["uri"].lower() assert "dart" in definition_location["uri"].lower()
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test finding symbols in the full symbol tree.""" """Test finding symbols in the full symbol tree."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -169,7 +169,7 @@ class TestDartLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "MathHelper"), "MathHelper class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "MathHelper"), "MathHelper class not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "User"), "User class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "User"), "User class not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test finding references using symbol selection range.""" """Test finding references using symbol selection range."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -202,7 +202,7 @@ class TestDartLanguageServer:
"main.dart should reference add method (tried all positions in selectionRange)" "main.dart should reference add method (tried all positions in selectionRange)"
) )
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a method.""" """Test request_containing_symbol for a method."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -217,7 +217,7 @@ class TestDartLanguageServer:
body = containing_symbol["body"].get_text() body = containing_symbol["body"].get_text()
assert "add" in body or "final result" in body assert "add" in body or "final result" in body
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a class.""" """Test request_containing_symbol for a class."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -229,7 +229,7 @@ class TestDartLanguageServer:
assert containing_symbol["name"] == "Calculator" assert containing_symbol["name"] == "Calculator"
assert containing_symbol["kind"] == SymbolKind.Class assert containing_symbol["kind"] == SymbolKind.Class
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol with nested scopes.""" """Test request_containing_symbol with nested scopes."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -241,7 +241,7 @@ class TestDartLanguageServer:
assert containing_symbol["name"] == "add" assert containing_symbol["name"] == "add"
assert containing_symbol["kind"] == SymbolKind.Method assert containing_symbol["kind"] == SymbolKind.Method
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a variable usage.""" """Test request_defining_symbol for a variable usage."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -259,7 +259,7 @@ class TestDartLanguageServer:
if defining_symbol.get("name") == "add": if defining_symbol.get("name") == "add":
assert defining_symbol.get("kind") == SymbolKind.Method.value assert defining_symbol.get("kind") == SymbolKind.Method.value
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for an imported class/function.""" """Test request_defining_symbol for an imported class/function."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -272,7 +272,7 @@ class TestDartLanguageServer:
# Could be Function or Method depending on language server interpretation # Could be Function or Method depending on language server interpretation
assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value] assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value]
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_defining_symbol_class_method(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_class_method(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a static class method.""" """Test request_defining_symbol for a static class method."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -284,7 +284,7 @@ class TestDartLanguageServer:
assert defining_symbol.get("name") == "power" assert defining_symbol.get("name") == "power"
assert defining_symbol.get("kind") == SymbolKind.Method.value assert defining_symbol.get("kind") == SymbolKind.Method.value
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test getting document symbols from a Dart file.""" """Test getting document symbols from a Dart file."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -311,7 +311,7 @@ class TestDartLanguageServer:
# This is acceptable behavior - the important thing is we found the class # This is acceptable behavior - the important thing is we found the class
pass pass
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_request_referencing_symbols_comprehensive(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_comprehensive(self, language_server: SolidLanguageServer) -> None:
"""Test comprehensive referencing symbols functionality.""" """Test comprehensive referencing symbols functionality."""
file_path = os.path.join("lib", "main.dart") file_path = os.path.join("lib", "main.dart")
@@ -340,7 +340,7 @@ class TestDartLanguageServer:
assert "start" in ref["range"] assert "start" in ref["range"]
assert "end" in ref["range"] assert "end" in ref["range"]
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_cross_file_symbol_resolution(self, language_server: SolidLanguageServer) -> None: def test_cross_file_symbol_resolution(self, language_server: SolidLanguageServer) -> None:
"""Test symbol resolution across multiple files.""" """Test symbol resolution across multiple files."""
helper_file_path = os.path.join("lib", "helper.dart") helper_file_path = os.path.join("lib", "helper.dart")
@@ -363,7 +363,7 @@ class TestDartLanguageServer:
for ref in main_dart_refs: for ref in main_dart_refs:
assert "range" in ref or "location" in ref assert "range" in ref or "location" in ref
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -376,7 +376,7 @@ class TestDartLanguageServer:
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_symbol_body_contains_full_method(self, language_server: SolidLanguageServer) -> None: def test_symbol_body_contains_full_method(self, language_server: SolidLanguageServer) -> None:
"""Test that document symbols return the full method body range, not just the identifier. """Test that document symbols return the full method body range, not just the identifier.
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.dart @pytest.mark.dart
class TestDartDiagnostics: class TestDartDiagnostics:
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.DART], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+12 -9
View File
@@ -10,20 +10,23 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
# These marks will be applied to all tests in this module # These marks will be applied to all tests in this module
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(not language_tests_enabled(Language.ELIXIR), reason="Elixir tests are disabled")] pytestmark = [
pytest.mark.elixir,
pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ELIXIR), reason="Elixir tests are disabled"),
]
class TestElixirBasic: class TestElixirBasic:
"""Basic Elixir language server functionality tests.""" """Basic Elixir language server functionality tests."""
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_references_function_definition(self, language_server: SolidLanguageServer): def test_request_references_function_definition(self, language_server: SolidLanguageServer):
"""Test finding references to a function definition.""" """Test finding references to a function definition."""
file_path = os.path.join("lib", "models.ex") file_path = os.path.join("lib", "models.ex")
@@ -52,7 +55,7 @@ class TestElixirBasic:
found_definition = any(ref["uri"].endswith("models.ex") for ref in references) found_definition = any(ref["uri"].endswith("models.ex") for ref in references)
assert found_definition, "Should find the function definition" assert found_definition, "Should find the function definition"
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_references_create_user_function(self, language_server: SolidLanguageServer): def test_request_references_create_user_function(self, language_server: SolidLanguageServer):
"""Test finding references to create_user function.""" """Test finding references to create_user function."""
file_path = os.path.join("lib", "services.ex") file_path = os.path.join("lib", "services.ex")
@@ -77,7 +80,7 @@ class TestElixirBasic:
assert references is not None assert references is not None
assert len(references) > 0 assert len(references) > 0
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer): def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer):
"""Test finding symbols that reference a specific function.""" """Test finding symbols that reference a specific function."""
file_path = os.path.join("lib", "models.ex") file_path = os.path.join("lib", "models.ex")
@@ -101,7 +104,7 @@ class TestElixirBasic:
assert referencing_symbols is not None assert referencing_symbols is not None
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_timeout_enumeration_bug(self, language_server: SolidLanguageServer): def test_timeout_enumeration_bug(self, language_server: SolidLanguageServer):
"""Test that enumeration doesn't timeout (regression test).""" """Test that enumeration doesn't timeout (regression test)."""
# This should complete without timing out # This should complete without timing out
@@ -113,7 +116,7 @@ class TestElixirBasic:
symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots() symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
assert symbols is not None assert symbols is not None
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -139,7 +142,7 @@ class TestElixirBasic:
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
@@ -5,11 +5,14 @@ from pathlib import Path
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled, start_ls_context from test.conftest import language_server_tests_enabled, start_ls_context
# These marks will be applied to all tests in this module # These marks will be applied to all tests in this module
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(not language_tests_enabled(Language.ELIXIR), reason="Elixir tests are disabled")] pytestmark = [
pytest.mark.elixir,
pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ELIXIR), reason="Elixir tests are disabled"),
]
# Skip slow tests in CI - they require multiple Expert instances which is too slow # Skip slow tests in CI - they require multiple Expert instances which is too slow
IN_CI = bool(os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS")) IN_CI = bool(os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"))
@@ -26,7 +29,7 @@ def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
Uses session scope to avoid restarting Expert for each test. Uses session scope to avoid restarting Expert for each test.
""" """
ignored_paths = ["scripts", "ignored_dir"] ignored_paths = ["scripts", "ignored_dir"]
with start_ls_context(language=Language.ELIXIR, ignored_paths=ignored_paths) as ls: with start_ls_context(ls_id=LanguageServerId.ELIXIR, ignored_paths=ignored_paths) as ls:
yield ls yield ls
@@ -81,7 +84,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
@pytest.mark.slow @pytest.mark.slow
@SKIP_SLOW_IN_CI @SKIP_SLOW_IN_CI
@pytest.mark.parametrize("repo_path", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ELIXIR], indirect=True)
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None: def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
"""Tests that refs and symbols with glob patterns are ignored. """Tests that refs and symbols with glob patterns are ignored.
@@ -89,7 +92,7 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
which adds ~60-90s startup time. which adds ~60-90s startup time.
""" """
ignored_paths = ["*cripts", "ignored_*"] # codespell:ignore cripts ignored_paths = ["*cripts", "ignored_*"] # codespell:ignore cripts
with start_ls_context(language=Language.ELIXIR, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls: with start_ls_context(ls_id=LanguageServerId.ELIXIR, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
# Same as in the above tests # Same as in the above tests
root = ls.request_full_symbol_tree()[0] root = ls.request_full_symbol_tree()[0]
root_children = root["children"] root_children = root["children"]
@@ -121,7 +124,7 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored (glob)" assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored (glob)"
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_default_ignored_directories(language_server: SolidLanguageServer): def test_default_ignored_directories(language_server: SolidLanguageServer):
"""Test that default Elixir directories are ignored.""" """Test that default Elixir directories are ignored."""
# Test that Elixir-specific directories are ignored by default # Test that Elixir-specific directories are ignored by default
@@ -142,7 +145,7 @@ def test_default_ignored_directories(language_server: SolidLanguageServer):
reason="Expert 0.1.0 bug: document_symbols may return nil for some files (flaky)", reason="Expert 0.1.0 bug: document_symbols may return nil for some files (flaky)",
raises=Exception, raises=Exception,
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer): def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer):
"""Test that symbol tree excludes build and dependency directories.""" """Test that symbol tree excludes build and dependency directories."""
symbol_tree = language_server.request_full_symbol_tree() symbol_tree = language_server.request_full_symbol_tree()
@@ -12,11 +12,14 @@ import pytest
from serena.project import Project from serena.project import Project
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
# These marks will be applied to all tests in this module # These marks will be applied to all tests in this module
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(not language_tests_enabled(Language.ELIXIR), reason="Elixir tests are disabled")] pytestmark = [
pytest.mark.elixir,
pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ELIXIR), reason="Elixir tests are disabled"),
]
class TestElixirIntegration: class TestElixirIntegration:
@@ -42,7 +45,7 @@ class TestElixirIntegration:
assert (repo_path / "test" / "test_repo_test.exs").exists(), "test file should exist" assert (repo_path / "test" / "test_repo_test.exs").exists(), "test file should exist"
assert (repo_path / "test" / "models_test.exs").exists(), "models test should exist" assert (repo_path / "test" / "models_test.exs").exists(), "models test should exist"
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_cross_file_symbol_resolution(self, language_server: SolidLanguageServer): def test_cross_file_symbol_resolution(self, language_server: SolidLanguageServer):
"""Test that symbols can be resolved across different files.""" """Test that symbols can be resolved across different files."""
# Test that User struct from models.ex can be found when referenced in services.ex # Test that User struct from models.ex can be found when referenced in services.ex
@@ -67,7 +70,7 @@ class TestElixirIntegration:
# Should point to models.ex # Should point to models.ex
assert "models.ex" in defining_symbol["location"]["uri"] assert "models.ex" in defining_symbol["location"]["uri"]
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_module_hierarchy_understanding(self, language_server: SolidLanguageServer): def test_module_hierarchy_understanding(self, language_server: SolidLanguageServer):
"""Test that the language server understands Elixir module hierarchy.""" """Test that the language server understands Elixir module hierarchy."""
models_file = os.path.join("lib", "models.ex") models_file = os.path.join("lib", "models.ex")
@@ -91,7 +94,7 @@ class TestElixirIntegration:
def test_file_extension_matching(self): def test_file_extension_matching(self):
"""Test that the Elixir language recognizes the correct file extensions.""" """Test that the Elixir language recognizes the correct file extensions."""
language = Language.ELIXIR language = LanguageServerId.ELIXIR
matcher = language.get_source_fn_matcher() matcher = language.get_source_fn_matcher()
# Test Elixir file extensions # Test Elixir file extensions
@@ -110,7 +113,7 @@ class TestElixirIntegration:
class TestElixirProject: class TestElixirProject:
@pytest.mark.parametrize("project", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("project", [LanguageServerId.ELIXIR], indirect=True)
def test_comprehensive_symbol_search(self, project: Project): def test_comprehensive_symbol_search(self, project: Project):
"""Test comprehensive symbol search across the entire project.""" """Test comprehensive symbol search across the entire project."""
# Search for all function definitions # Search for all function definitions
@@ -138,7 +141,7 @@ class TestElixirProject:
models_structs = [m for m in struct_matches if m.source_file_path and "models.ex" in m.source_file_path] models_structs = [m for m in struct_matches if m.source_file_path and "models.ex" in m.source_file_path]
assert len(models_structs) > 0, "Should find struct definitions in models.ex" assert len(models_structs) > 0, "Should find struct definitions in models.ex"
@pytest.mark.parametrize("project", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("project", [LanguageServerId.ELIXIR], indirect=True)
def test_protocol_and_implementation_understanding(self, project: Project): def test_protocol_and_implementation_understanding(self, project: Project):
"""Test that the language server understands Elixir protocols and implementations.""" """Test that the language server understands Elixir protocols and implementations."""
# Search for protocol definitions # Search for protocol definitions
@@ -12,12 +12,15 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
# These marks will be applied to all tests in this module # These marks will be applied to all tests in this module
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(not language_tests_enabled(Language.ELIXIR), reason="Elixir tests are disabled")] pytestmark = [
pytest.mark.elixir,
pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ELIXIR), reason="Elixir tests are disabled"),
]
class TestElixirLanguageServerSymbols: class TestElixirLanguageServerSymbols:
@@ -26,7 +29,7 @@ class TestElixirLanguageServerSymbols:
@pytest.mark.xfail( @pytest.mark.xfail(
reason="Expert 0.1.0 bug: document_symbols returns nil for some files (FunctionClauseError in XPExpert.EngineApi.document_symbols/2)" reason="Expert 0.1.0 bug: document_symbols returns nil for some files (FunctionClauseError in XPExpert.EngineApi.document_symbols/2)"
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a function.""" """Test request_containing_symbol for a function."""
# Test for a position inside the create_user function # Test for a position inside the create_user function
@@ -54,7 +57,7 @@ class TestElixirLanguageServerSymbols:
if "body" in containing_symbol: if "body" in containing_symbol:
assert "def create_user" in containing_symbol["body"].get_text() assert "def create_user" in containing_symbol["body"].get_text()
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a module.""" """Test request_containing_symbol for a module."""
# Test for a position inside the UserService module but outside any function # Test for a position inside the UserService module but outside any function
@@ -79,7 +82,7 @@ class TestElixirLanguageServerSymbols:
assert "UserService" in containing_symbol["name"] assert "UserService" in containing_symbol["name"]
assert containing_symbol["kind"] == SymbolKind.Module or containing_symbol["kind"] == SymbolKind.Class assert containing_symbol["kind"] == SymbolKind.Module or containing_symbol["kind"] == SymbolKind.Class
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol with nested scopes.""" """Test request_containing_symbol with nested scopes."""
# Test for a position inside a function which is inside a module # Test for a position inside a function which is inside a module
@@ -104,7 +107,7 @@ class TestElixirLanguageServerSymbols:
expected_names = ["create_user", "UserService"] expected_names = ["create_user", "UserService"]
assert any(name in containing_symbol["name"] for name in expected_names) assert any(name in containing_symbol["name"] for name in expected_names)
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a position with no containing symbol.""" """Test request_containing_symbol for a position with no containing symbol."""
# Test for a position outside any function/module (e.g., in module doc) # Test for a position outside any function/module (e.g., in module doc)
@@ -116,7 +119,7 @@ class TestElixirLanguageServerSymbols:
# This is acceptable behavior for module-level positions # This is acceptable behavior for module-level positions
assert containing_symbol is None or containing_symbol == {} or "TestRepo.Services" in str(containing_symbol) assert containing_symbol is None or containing_symbol == {} or "TestRepo.Services" in str(containing_symbol)
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_referencing_symbols_struct(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_struct(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a struct.""" """Test request_referencing_symbols for a struct."""
# Test referencing symbols for User struct # Test referencing symbols for User struct
@@ -146,7 +149,7 @@ class TestElixirLanguageServerSymbols:
# We expect some references from services.ex # We expect some references from services.ex
assert len(services_references) >= 0 # At least attempt to find references assert len(services_references) >= 0 # At least attempt to find references
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a position with no symbol.""" """Test request_referencing_symbols for a position with no symbol."""
file_path = os.path.join("lib", "services.ex") file_path = os.path.join("lib", "services.ex")
@@ -164,7 +167,7 @@ class TestElixirLanguageServerSymbols:
@pytest.mark.xfail( @pytest.mark.xfail(
reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)" reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)"
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_defining_symbol_function_call(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_function_call(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a function call.""" """Test request_defining_symbol for a function call."""
# Find a place where User.new is called in services.ex # Find a place where User.new is called in services.ex
@@ -191,7 +194,7 @@ class TestElixirLanguageServerSymbols:
@pytest.mark.xfail( @pytest.mark.xfail(
reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)" reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)"
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_defining_symbol_struct_usage(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_struct_usage(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a struct usage.""" """Test request_defining_symbol for a struct usage."""
# Find a place where User struct is used in services.ex # Find a place where User struct is used in services.ex
@@ -215,7 +218,7 @@ class TestElixirLanguageServerSymbols:
@pytest.mark.xfail( @pytest.mark.xfail(
reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)" reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)"
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a position with no symbol.""" """Test request_defining_symbol for a position with no symbol."""
# Test for a position with no symbol (e.g., whitespace or comment) # Test for a position with no symbol (e.g., whitespace or comment)
@@ -226,7 +229,7 @@ class TestElixirLanguageServerSymbols:
# Should return None or empty # Should return None or empty
assert defining_symbol is None or defining_symbol == {} assert defining_symbol is None or defining_symbol == {}
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None: def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
"""Test integration between different symbol methods.""" """Test integration between different symbol methods."""
file_path = os.path.join("lib", "models.ex") file_path = os.path.join("lib", "models.ex")
@@ -257,7 +260,7 @@ class TestElixirLanguageServerSymbols:
assert isinstance(refs, list) assert isinstance(refs, list)
@pytest.mark.xfail(reason="Flaky test, sometimes fails with an Expert-internal error") @pytest.mark.xfail(reason="Flaky test, sometimes fails with an Expert-internal error")
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None: def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
"""Test that symbol tree structure is correctly built.""" """Test that symbol tree structure is correctly built."""
symbol_tree = language_server.request_full_symbol_tree() symbol_tree = language_server.request_full_symbol_tree()
@@ -283,7 +286,7 @@ class TestElixirLanguageServerSymbols:
found_modules = [name for name in expected_modules if name in file_names] found_modules = [name for name in expected_modules if name in file_names]
assert len(found_modules) > 0, f"Expected to find some modules from {expected_modules}, but got {file_names}" assert len(found_modules) > 0, f"Expected to find some modules from {expected_modules}, but got {file_names}"
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None: def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
"""Test request_dir_overview functionality.""" """Test request_dir_overview functionality."""
lib_overview = language_server.request_dir_overview("lib") lib_overview = language_server.request_dir_overview("lib")
@@ -321,7 +324,7 @@ class TestElixirLanguageServerSymbols:
# found_terms = [term for term in expected_terms if term in overview_text] # found_terms = [term for term in expected_terms if term in overview_text]
# assert len(found_terms) > 0, f"Expected to find some terms from {expected_terms} in overview" # assert len(found_terms) > 0, f"Expected to find some terms from {expected_terms} in overview"
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELIXIR], indirect=True)
def test_containing_symbol_of_module_attribute(self, language_server: SolidLanguageServer) -> None: def test_containing_symbol_of_module_attribute(self, language_server: SolidLanguageServer) -> None:
"""Test containing symbol for module attributes.""" """Test containing symbol for module attributes."""
file_path = os.path.join("lib", "models.ex") file_path = os.path.join("lib", "models.ex")
+7 -7
View File
@@ -3,16 +3,16 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(not language_tests_enabled(Language.ELM), reason="Elm tests are disabled (elm compiler not available)") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ELM), reason="Elm tests are disabled (elm compiler not available)")
@pytest.mark.elm @pytest.mark.elm
class TestElmLanguageServer: class TestElmLanguageServer:
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELM], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "greet"), "greet function not found in symbol tree"
@@ -20,7 +20,7 @@ class TestElmLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "formatMessage"), "formatMessage function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "formatMessage"), "formatMessage function not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "addNumbers"), "addNumbers function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "addNumbers"), "addNumbers function not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELM], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None: def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("Main.elm") file_path = os.path.join("Main.elm")
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
@@ -34,7 +34,7 @@ class TestElmLanguageServer:
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Main.elm should reference greet function" assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Main.elm should reference greet function"
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELM], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None: def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
# Test formatMessage function which is defined in Utils.elm and used in Main.elm # Test formatMessage function which is defined in Utils.elm and used in Main.elm
utils_path = os.path.join("Utils.elm") utils_path = os.path.join("Utils.elm")
@@ -56,7 +56,7 @@ class TestElmLanguageServer:
# Verify that at least one reference is in Main.elm (where formatMessage is used) # 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" 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) @pytest.mark.parametrize("language_server", [LanguageServerId.ELM], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+2 -2
View File
@@ -3,14 +3,14 @@ import shutil
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.elm @pytest.mark.elm
@pytest.mark.skipif(shutil.which("node") is None or shutil.which("npm") is None, reason="Elm diagnostics require Node.js and npm") @pytest.mark.skipif(shutil.which("node") is None or shutil.which("npm") is None, reason="Elm diagnostics require Node.js and npm")
class TestElmDiagnostics: class TestElmDiagnostics:
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ELM], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+8 -8
View File
@@ -8,24 +8,24 @@ like request_references using the test repository.
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.erlang @pytest.mark.erlang
@pytest.mark.skipif(not language_tests_enabled(Language.ERLANG), reason="Erlang tests are disabled") @pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ERLANG), reason="Erlang tests are disabled")
class TestErlangLanguageServerBasics: class TestErlangLanguageServerBasics:
"""Test basic functionality of the Erlang language server.""" """Test basic functionality of the Erlang language server."""
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_language_server_initialization(self, language_server: SolidLanguageServer) -> None: def test_language_server_initialization(self, language_server: SolidLanguageServer) -> None:
"""Test that the Erlang language server initializes properly.""" """Test that the Erlang language server initializes properly."""
assert language_server is not None assert language_server is not None
assert language_server.language == Language.ERLANG assert language_server.ls_id == LanguageServerId.ERLANG
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test document symbols retrieval for Erlang files.""" """Test document symbols retrieval for Erlang files."""
try: try:
@@ -43,7 +43,7 @@ class TestErlangLanguageServerBasics:
else: else:
raise raise
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -56,7 +56,7 @@ class TestErlangLanguageServerBasics:
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
@@ -4,13 +4,13 @@ from pathlib import Path
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.conftest import language_tests_enabled, start_ls_context from test.conftest import language_server_tests_enabled, start_ls_context
# These marks will be applied to all tests in this module # These marks will be applied to all tests in this module
pytestmark = [ pytestmark = [
pytest.mark.erlang, pytest.mark.erlang,
pytest.mark.skipif(not language_tests_enabled(Language.ERLANG), reason="Erlang tests are disabled"), pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ERLANG), reason="Erlang tests are disabled"),
] ]
@@ -18,13 +18,13 @@ pytestmark = [
def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]: def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
"""Fixture to set up an LS for the erlang test repo with the 'ignored_dir' directory ignored.""" """Fixture to set up an LS for the erlang test repo with the 'ignored_dir' directory ignored."""
ignored_paths = ["_build", "ignored_dir"] ignored_paths = ["_build", "ignored_dir"]
with start_ls_context(language=Language.ERLANG, ignored_paths=ignored_paths) as ls: with start_ls_context(ls_id=LanguageServerId.ERLANG, ignored_paths=ignored_paths) as ls:
yield ls yield ls
@pytest.mark.timeout(60) # Add 60 second timeout @pytest.mark.timeout(60) # Add 60 second timeout
@pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False) @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("ls_with_ignored_dirs", [LanguageServerId.ERLANG], indirect=True)
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer): def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
"""Tests that request_full_symbol_tree ignores the configured directory.""" """Tests that request_full_symbol_tree ignores the configured directory."""
root = ls_with_ignored_dirs.request_full_symbol_tree()[0] root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
@@ -41,7 +41,7 @@ def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
@pytest.mark.timeout(60) # Add 60 second timeout @pytest.mark.timeout(60) # Add 60 second timeout
@pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False) @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("ls_with_ignored_dirs", [LanguageServerId.ERLANG], indirect=True)
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer): def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
"""Tests that find_references ignores the configured directory.""" """Tests that find_references ignores the configured directory."""
# Location of user record, which might be referenced in ignored_dir # Location of user record, which might be referenced in ignored_dir
@@ -68,11 +68,11 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
@pytest.mark.timeout(60) # Add 60 second timeout @pytest.mark.timeout(60) # Add 60 second timeout
@pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False) @pytest.mark.xfail(reason="Known timeout issue on Ubuntu CI with Erlang LS server startup", strict=False)
@pytest.mark.parametrize("repo_path", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("repo_path", [LanguageServerId.ERLANG], indirect=True)
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None: def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
"""Tests that refs and symbols with glob patterns are ignored.""" """Tests that refs and symbols with glob patterns are ignored."""
ignored_paths = ["_build*", "ignored_*", "*.tmp"] ignored_paths = ["_build*", "ignored_*", "*.tmp"]
with start_ls_context(language=Language.ERLANG, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls: with start_ls_context(ls_id=LanguageServerId.ERLANG, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
# Same as in the above tests # Same as in the above tests
root = ls.request_full_symbol_tree()[0] root = ls.request_full_symbol_tree()[0]
root_children = root["children"] root_children = root["children"]
@@ -105,7 +105,7 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored (glob)" assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored (glob)"
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_default_ignored_directories(language_server: SolidLanguageServer): def test_default_ignored_directories(language_server: SolidLanguageServer):
"""Test that default Erlang directories are ignored.""" """Test that default Erlang directories are ignored."""
# Test that Erlang-specific directories are ignored by default # Test that Erlang-specific directories are ignored by default
@@ -123,7 +123,7 @@ def test_default_ignored_directories(language_server: SolidLanguageServer):
assert not language_server.is_ignored_dirname("priv"), "priv should not be ignored" assert not language_server.is_ignored_dirname("priv"), "priv should not be ignored"
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer): def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer):
"""Test that symbol tree excludes build and dependency directories.""" """Test that symbol tree excludes build and dependency directories."""
symbol_tree = language_server.request_full_symbol_tree() symbol_tree = language_server.request_full_symbol_tree()
@@ -143,7 +143,7 @@ def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer):
assert len(found_important) > 0, f"Expected to find important directories: {important_dirs}, got: {children_names}" assert len(found_important) > 0, f"Expected to find important directories: {important_dirs}, got: {children_names}"
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_ignore_compiled_files(language_server: SolidLanguageServer): def test_ignore_compiled_files(language_server: SolidLanguageServer):
"""Test that compiled Erlang files are ignored.""" """Test that compiled Erlang files are ignored."""
# Test that beam files are ignored # Test that beam files are ignored
@@ -155,7 +155,7 @@ def test_ignore_compiled_files(language_server: SolidLanguageServer):
assert not language_server.is_ignored_filename("records.hrl"), "Header files should not be ignored" assert not language_server.is_ignored_filename("records.hrl"), "Header files should not be ignored"
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_rebar_directories_ignored(language_server: SolidLanguageServer): def test_rebar_directories_ignored(language_server: SolidLanguageServer):
"""Test that rebar-specific directories are ignored.""" """Test that rebar-specific directories are ignored."""
# Test rebar3-specific directories # Test rebar3-specific directories
@@ -168,7 +168,7 @@ def test_rebar_directories_ignored(language_server: SolidLanguageServer):
assert not language_server.is_ignored_filename("rebar.lock"), "rebar.lock should not be ignored" assert not language_server.is_ignored_filename("rebar.lock"), "rebar.lock should not be ignored"
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("ls_with_ignored_dirs", [LanguageServerId.ERLANG], indirect=True)
def test_document_symbols_ignores_dirs(ls_with_ignored_dirs: SolidLanguageServer): def test_document_symbols_ignores_dirs(ls_with_ignored_dirs: SolidLanguageServer):
"""Test that document symbols from ignored directories are not included.""" """Test that document symbols from ignored directories are not included."""
# Try to get symbols from a file in ignored directory (should not find it) # Try to get symbols from a file in ignored directory (should not find it)
@@ -183,7 +183,7 @@ def test_document_symbols_ignores_dirs(ls_with_ignored_dirs: SolidLanguageServer
pass pass
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_erlang_specific_ignore_patterns(language_server: SolidLanguageServer): def test_erlang_specific_ignore_patterns(language_server: SolidLanguageServer):
"""Test Erlang-specific ignore patterns work correctly.""" """Test Erlang-specific ignore patterns work correctly."""
erlang_ignored_dirs = ["_build", "ebin", ".rebar3", "_checkouts", "cover"] erlang_ignored_dirs = ["_build", "ebin", ".rebar3", "_checkouts", "cover"]
@@ -12,21 +12,21 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
# These marks will be applied to all tests in this module # These marks will be applied to all tests in this module
pytestmark = [ pytestmark = [
pytest.mark.erlang, pytest.mark.erlang,
pytest.mark.skipif(not language_tests_enabled(Language.ERLANG), reason="Erlang tests are disabled"), pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.ERLANG), reason="Erlang tests are disabled"),
] ]
class TestErlangLanguageServerSymbols: class TestErlangLanguageServerSymbols:
"""Test the Erlang language server's symbol-related functionality.""" """Test the Erlang language server's symbol-related functionality."""
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a function.""" """Test request_containing_symbol for a function."""
# Test for a position inside the create_user function # Test for a position inside the create_user function
@@ -53,7 +53,7 @@ class TestErlangLanguageServerSymbols:
if "body" in containing_symbol: if "body" in containing_symbol:
assert "create_user" in containing_symbol["body"].get_text() assert "create_user" in containing_symbol["body"].get_text()
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a module.""" """Test request_containing_symbol for a module."""
# Test for a position inside the models module but outside any function # Test for a position inside the models module but outside any function
@@ -78,7 +78,7 @@ class TestErlangLanguageServerSymbols:
assert "models" in containing_symbol["name"] or "module" in containing_symbol["name"].lower() assert "models" in containing_symbol["name"] or "module" in containing_symbol["name"].lower()
assert containing_symbol["kind"] == SymbolKind.Module or containing_symbol["kind"] == SymbolKind.Class assert containing_symbol["kind"] == SymbolKind.Module or containing_symbol["kind"] == SymbolKind.Class
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol with nested scopes.""" """Test request_containing_symbol with nested scopes."""
# Test for a position inside a function which is inside a module # Test for a position inside a function which is inside a module
@@ -107,7 +107,7 @@ class TestErlangLanguageServerSymbols:
expected_names = ["create_user", "models"] expected_names = ["create_user", "models"]
assert any(name in containing_symbol["name"] for name in expected_names) assert any(name in containing_symbol["name"] for name in expected_names)
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a position with no containing symbol.""" """Test request_containing_symbol for a position with no containing symbol."""
# Test for a position outside any function/module (e.g., in comments) # Test for a position outside any function/module (e.g., in comments)
@@ -119,7 +119,7 @@ class TestErlangLanguageServerSymbols:
# This is acceptable behavior for module-level positions # This is acceptable behavior for module-level positions
assert containing_symbol is None or containing_symbol == {} or "models" in str(containing_symbol) assert containing_symbol is None or containing_symbol == {} or "models" in str(containing_symbol)
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_referencing_symbols_record(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_record(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a record.""" """Test request_referencing_symbols for a record."""
# Test referencing symbols for user record # Test referencing symbols for user record
@@ -149,7 +149,7 @@ class TestErlangLanguageServerSymbols:
# We expect some references from models.erl # We expect some references from models.erl
assert len(models_references) >= 0 # At least attempt to find references assert len(models_references) >= 0 # At least attempt to find references
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a function.""" """Test request_referencing_symbols for a function."""
# Test referencing symbols for create_user function # Test referencing symbols for create_user function
@@ -181,7 +181,7 @@ class TestErlangLanguageServerSymbols:
] ]
assert len(service_references) >= 0 # At least attempt to find references assert len(service_references) >= 0 # At least attempt to find references
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a position with no symbol.""" """Test request_referencing_symbols for a position with no symbol."""
file_path = os.path.join("src", "models.erl") file_path = os.path.join("src", "models.erl")
@@ -196,7 +196,7 @@ class TestErlangLanguageServerSymbols:
pass pass
# Tests for request_defining_symbol # Tests for request_defining_symbol
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_defining_symbol_function_call(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_function_call(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a function call.""" """Test request_defining_symbol for a function call."""
# Find a place where models:create_user is called in services.erl # Find a place where models:create_user is called in services.erl
@@ -220,7 +220,7 @@ class TestErlangLanguageServerSymbols:
if "location" in defining_symbol and "uri" in defining_symbol["location"]: if "location" in defining_symbol and "uri" in defining_symbol["location"]:
assert "models.erl" in defining_symbol["location"]["uri"] assert "models.erl" in defining_symbol["location"]["uri"]
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_defining_symbol_record_usage(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_record_usage(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a record usage.""" """Test request_defining_symbol for a record usage."""
# Find a place where #user{} record is used in models.erl # Find a place where #user{} record is used in models.erl
@@ -243,7 +243,7 @@ class TestErlangLanguageServerSymbols:
if "location" in defining_symbol and "uri" in defining_symbol["location"]: if "location" in defining_symbol and "uri" in defining_symbol["location"]:
assert "records.hrl" in defining_symbol["location"]["uri"] assert "records.hrl" in defining_symbol["location"]["uri"]
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_defining_symbol_module_call(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_module_call(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a module function call.""" """Test request_defining_symbol for a module function call."""
# Find a place where utils:validate_input is called # Find a place where utils:validate_input is called
@@ -264,7 +264,7 @@ class TestErlangLanguageServerSymbols:
if defining_symbol: if defining_symbol:
assert "validate" in defining_symbol.get("name", "") or "email" in defining_symbol.get("name", "") assert "validate" in defining_symbol.get("name", "") or "email" in defining_symbol.get("name", "")
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a position with no symbol.""" """Test request_defining_symbol for a position with no symbol."""
# Test for a position with no symbol (e.g., whitespace or comment) # Test for a position with no symbol (e.g., whitespace or comment)
@@ -275,7 +275,7 @@ class TestErlangLanguageServerSymbols:
# Should return None or empty # Should return None or empty
assert defining_symbol is None or defining_symbol == {} assert defining_symbol is None or defining_symbol == {}
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None: def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
"""Test integration between different symbol methods.""" """Test integration between different symbol methods."""
file_path = os.path.join("src", "models.erl") file_path = os.path.join("src", "models.erl")
@@ -310,7 +310,7 @@ class TestErlangLanguageServerSymbols:
reason="Known intermittent timeout issue in Erlang LS in CI environments. May pass locally but can timeout on slower CI systems.", reason="Known intermittent timeout issue in Erlang LS in CI environments. May pass locally but can timeout on slower CI systems.",
strict=False, strict=False,
) )
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None: def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
"""Test that symbol tree structure is correctly built.""" """Test that symbol tree structure is correctly built."""
symbol_tree = language_server.request_full_symbol_tree() symbol_tree = language_server.request_full_symbol_tree()
@@ -336,7 +336,7 @@ class TestErlangLanguageServerSymbols:
found_modules = [name for name in expected_modules if any(name in fname for fname in file_names)] found_modules = [name for name in expected_modules if any(name in fname for fname in file_names)]
assert len(found_modules) > 0, f"Expected to find some modules from {expected_modules}, but got {file_names}" assert len(found_modules) > 0, f"Expected to find some modules from {expected_modules}, but got {file_names}"
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None: def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
"""Test request_dir_overview functionality.""" """Test request_dir_overview functionality."""
src_overview = language_server.request_dir_overview("src") src_overview = language_server.request_dir_overview("src")
@@ -353,7 +353,7 @@ class TestErlangLanguageServerSymbols:
found_terms = [term for term in expected_terms if term in overview_text] found_terms = [term for term in expected_terms if term in overview_text]
assert len(found_terms) > 0, f"Expected to find some terms from {expected_terms} in overview" assert len(found_terms) > 0, f"Expected to find some terms from {expected_terms} in overview"
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_containing_symbol_of_record_field(self, language_server: SolidLanguageServer) -> None: def test_containing_symbol_of_record_field(self, language_server: SolidLanguageServer) -> None:
"""Test containing symbol for record field access.""" """Test containing symbol for record field access."""
file_path = os.path.join("src", "models.erl") file_path = os.path.join("src", "models.erl")
@@ -378,7 +378,7 @@ class TestErlangLanguageServerSymbols:
expected_names = ["create_user", "update_user", "format_user_info"] expected_names = ["create_user", "update_user", "format_user_info"]
assert any(name in containing_symbol["name"] for name in expected_names) assert any(name in containing_symbol["name"] for name in expected_names)
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_containing_symbol_of_spec(self, language_server: SolidLanguageServer) -> None: def test_containing_symbol_of_spec(self, language_server: SolidLanguageServer) -> None:
"""Test containing symbol for function specs.""" """Test containing symbol for function specs."""
file_path = os.path.join("src", "models.erl") file_path = os.path.join("src", "models.erl")
@@ -410,7 +410,7 @@ class TestErlangLanguageServerSymbols:
"Similar to known Next LS timeout issues.", "Similar to known Next LS timeout issues.",
strict=False, strict=False,
) )
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.ERLANG], indirect=True)
def test_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None: def test_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None:
"""Test finding references across different files.""" """Test finding references across different files."""
# Test that we can find references to models module functions in services.erl # Test that we can find references to models module functions in services.erl
+16 -16
View File
@@ -8,10 +8,10 @@ Note: These tests require fortls to be installed: pip install fortls
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import find_identifier_position, get_repo_path, language_has_verified_implementation_support from test.conftest import find_identifier_position, get_repo_path, ls_has_verified_implementation_support
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Mark all tests in this module as fortran tests # Mark all tests in this module as fortran tests
@@ -21,7 +21,7 @@ pytestmark = pytest.mark.fortran
class TestFortranLanguageServer: class TestFortranLanguageServer:
"""Test Fortran language server functionality.""" """Test Fortran language server functionality."""
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test finding symbols using request_full_symbol_tree.""" """Test finding symbols using request_full_symbol_tree."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -39,11 +39,11 @@ class TestFortranLanguageServer:
# Verify subroutine symbol # Verify subroutine symbol
assert SymbolUtils.symbol_tree_contains_name(symbols, "print_result"), "print_result subroutine not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "print_result"), "print_result subroutine not found in symbol tree"
if language_has_verified_implementation_support(Language.FORTRAN): if ls_has_verified_implementation_support(LanguageServerId.FORTRAN):
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_find_implementations(self, language_server: SolidLanguageServer) -> None: def test_find_implementations(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.FORTRAN) repo_path = get_repo_path(LanguageServerId.FORTRAN)
pos = find_identifier_position(repo_path / "modules" / "geometry.f90", "distance") pos = find_identifier_position(repo_path / "modules" / "geometry.f90", "distance")
assert pos is not None, "Could not find interface distance in geometry.f90" assert pos is not None, "Could not find interface distance in geometry.f90"
@@ -53,9 +53,9 @@ class TestFortranLanguageServer:
assert implementation_files == {"modules/geometry.f90"}, f"Unexpected implementation locations: {implementations}" assert implementation_files == {"modules/geometry.f90"}, f"Unexpected implementation locations: {implementations}"
assert len(implementations) >= 2, f"Expected module procedure implementations, got: {implementations}" assert len(implementations) >= 2, f"Expected module procedure implementations, got: {implementations}"
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.FORTRAN) repo_path = get_repo_path(LanguageServerId.FORTRAN)
pos = find_identifier_position(repo_path / "modules" / "geometry.f90", "distance") pos = find_identifier_position(repo_path / "modules" / "geometry.f90", "distance")
assert pos is not None, "Could not find interface distance in geometry.f90" assert pos is not None, "Could not find interface distance in geometry.f90"
@@ -66,7 +66,7 @@ class TestFortranLanguageServer:
f"Expected distance_2d and distance_3d, got: {implementing_symbols}" f"Expected distance_2d and distance_3d, got: {implementing_symbols}"
) )
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_document_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test that document symbols can be retrieved from Fortran files.""" """Test that document symbols can be retrieved from Fortran files."""
# Test main.f90 - should have a program symbol # Test main.f90 - should have a program symbol
@@ -82,7 +82,7 @@ class TestFortranLanguageServer:
assert "multiply_numbers" in all_names, f"Function 'multiply_numbers' not found. Found: {all_names}" assert "multiply_numbers" in all_names, f"Function 'multiply_numbers' not found. Found: {all_names}"
assert "print_result" in all_names, f"Subroutine 'print_result' not found. Found: {all_names}" assert "print_result" in all_names, f"Subroutine 'print_result' not found. Found: {all_names}"
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_find_references_cross_file(self, language_server: SolidLanguageServer) -> None: def test_find_references_cross_file(self, language_server: SolidLanguageServer) -> None:
"""Test finding references across files using low-level request_references. """Test finding references across files using low-level request_references.
@@ -116,7 +116,7 @@ class TestFortranLanguageServer:
f"Expected to find reference in main.f90, but found references in: {[ref.get('relativePath') for ref in refs]}" f"Expected to find reference in main.f90, but found references in: {[ref.get('relativePath') for ref in refs]}"
) )
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_find_definition_cross_file(self, language_server: SolidLanguageServer) -> None: def test_find_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
"""Test finding definition across files using request_definition.""" """Test finding definition across files using request_definition."""
# In main.f90, line 7 (0-indexed: line 6) contains: result = add_numbers(5.0, 3.0) # In main.f90, line 7 (0-indexed: line 6) contains: result = add_numbers(5.0, 3.0)
@@ -142,7 +142,7 @@ class TestFortranLanguageServer:
f"Expected definition at line 4, but found at line {definition_location['range']['start']['line']}" f"Expected definition at line 4, but found at line {definition_location['range']['start']['line']}"
) )
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_request_referencing_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test finding symbols that reference a function - Serena's high-level API. """Test finding symbols that reference a function - Serena's high-level API.
@@ -189,7 +189,7 @@ class TestFortranLanguageServer:
# because it depends on finding containing symbols for each reference. We verify that # because it depends on finding containing symbols for each reference. We verify that
# the API works and returns valid symbols with proper structure. # the API works and returns valid symbols with proper structure.
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_request_defining_symbol(self, language_server: SolidLanguageServer) -> None: def test_request_defining_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test finding the defining symbol - Serena's high-level API. """Test finding the defining symbol - Serena's high-level API.
@@ -218,7 +218,7 @@ class TestFortranLanguageServer:
defining_path = defining_symbol["location"]["relativePath"] defining_path = defining_symbol["location"]["relativePath"]
assert "math_utils.f90" in defining_path, f"Expected definition to be in math_utils.f90, but found in: {defining_path}" assert "math_utils.f90" in defining_path, f"Expected definition to be in math_utils.f90, but found in: {defining_path}"
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_request_containing_symbol(self, language_server: SolidLanguageServer) -> None: def test_request_containing_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test finding the containing symbol for a position in the code.""" """Test finding the containing symbol for a position in the code."""
# Test finding the containing symbol for a position inside the add_numbers function # Test finding the containing symbol for a position inside the add_numbers function
@@ -246,7 +246,7 @@ class TestFortranLanguageServer:
assert "range" in location, "Location should contain range information" assert "range" in location, "Location should contain range information"
assert "start" in location["range"] and "end" in location["range"], "Range should have start and end positions" assert "start" in location["range"] and "end" in location["range"], "Range should have start and end positions"
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_type_and_interface_symbols(self, language_server: SolidLanguageServer) -> None: def test_type_and_interface_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test that type definitions and interfaces are properly recognized with corrected selectionRange. """Test that type definitions and interfaces are properly recognized with corrected selectionRange.
@@ -302,7 +302,7 @@ class TestFortranLanguageServer:
# refs might be empty if Point3D isn't used elsewhere, but the call should not fail # 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 # The important thing is that it doesn't error due to wrong character position
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.fortran @pytest.mark.fortran
class TestFortranDiagnostics: class TestFortranDiagnostics:
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FORTRAN], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+23 -20
View File
@@ -5,23 +5,26 @@ from typing import Any
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import ( from test.conftest import (
find_identifier_position, find_identifier_position,
get_repo_path, get_repo_path,
language_has_verified_implementation_support, language_server_tests_enabled,
language_tests_enabled, ls_has_verified_implementation_support,
) )
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
# Currently, most F# tests fail (regression/instability), so the suite is disabled on CI. # Currently, most F# tests fail (regression/instability), so the suite is disabled on CI.
pytestmark = [pytest.mark.fsharp, pytest.mark.skipif(not language_tests_enabled(Language.FSHARP), reason="F# tests are disabled")] pytestmark = [
pytest.mark.fsharp,
pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.FSHARP), reason="F# tests are disabled"),
]
class TestFSharpLanguageServer: class TestFSharpLanguageServer:
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
"""Test finding symbols in the full symbol tree.""" """Test finding symbols in the full symbol tree."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -35,7 +38,7 @@ class TestFSharpLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculatorClass"), "CalculatorClass not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculatorClass"), "CalculatorClass not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_get_document_symbols_program(self, language_server: SolidLanguageServer) -> None: def test_get_document_symbols_program(self, language_server: SolidLanguageServer) -> None:
"""Test getting document symbols from the main Program.fs file.""" """Test getting document symbols from the main Program.fs file."""
file_path = os.path.join("Program.fs") file_path = os.path.join("Program.fs")
@@ -45,11 +48,11 @@ class TestFSharpLanguageServer:
symbol_names = [s.get("name") for s in symbols] symbol_names = [s.get("name") for s in symbols]
assert "main" in symbol_names, "main function not found in Program.fs symbols" assert "main" in symbol_names, "main function not found in Program.fs symbols"
if language_has_verified_implementation_support(Language.FSHARP): if ls_has_verified_implementation_support(LanguageServerId.FSHARP):
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_find_implementations(self, language_server: SolidLanguageServer) -> None: def test_find_implementations(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.FSHARP) repo_path = get_repo_path(LanguageServerId.FSHARP)
pos = find_identifier_position(repo_path / "Formatter.fs", "FormatGreeting") pos = find_identifier_position(repo_path / "Formatter.fs", "FormatGreeting")
assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture" assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture"
@@ -59,9 +62,9 @@ class TestFSharpLanguageServer:
f"Expected ConsoleGreeter.FormatGreeting in implementations, got: {implementations}" f"Expected ConsoleGreeter.FormatGreeting in implementations, got: {implementations}"
) )
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.FSHARP) repo_path = get_repo_path(LanguageServerId.FSHARP)
pos = find_identifier_position(repo_path / "Formatter.fs", "FormatGreeting") pos = find_identifier_position(repo_path / "Formatter.fs", "FormatGreeting")
assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture" assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture"
@@ -72,7 +75,7 @@ class TestFSharpLanguageServer:
for symbol in implementing_symbols for symbol in implementing_symbols
), f"Expected ConsoleGreeter.FormatGreeting symbol, got: {implementing_symbols}" ), f"Expected ConsoleGreeter.FormatGreeting symbol, got: {implementing_symbols}"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_get_document_symbols_calculator(self, language_server: SolidLanguageServer) -> None: def test_get_document_symbols_calculator(self, language_server: SolidLanguageServer) -> None:
"""Test getting document symbols from Calculator.fs file.""" """Test getting document symbols from Calculator.fs file."""
file_path = os.path.join("Calculator.fs") file_path = os.path.join("Calculator.fs")
@@ -85,7 +88,7 @@ class TestFSharpLanguageServer:
for expected in expected_symbols: for expected in expected_symbols:
assert expected in symbol_names, f"{expected} function not found in Calculator.fs symbols" assert expected in symbol_names, f"{expected} function not found in Calculator.fs symbols"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test finding references using symbol selection range.""" """Test finding references using symbol selection range."""
file_path = os.path.join("Calculator.fs") file_path = os.path.join("Calculator.fs")
@@ -108,7 +111,7 @@ class TestFSharpLanguageServer:
# The add function should be referenced in Program.fs # The add function should be referenced in Program.fs
assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference add function" assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference add function"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_nested_module_symbols(self, language_server: SolidLanguageServer) -> None: def test_nested_module_symbols(self, language_server: SolidLanguageServer) -> None:
"""Test getting symbols from nested Models namespace.""" """Test getting symbols from nested Models namespace."""
file_path = os.path.join("Models", "Person.fs") file_path = os.path.join("Models", "Person.fs")
@@ -121,7 +124,7 @@ class TestFSharpLanguageServer:
for expected in expected_symbols: for expected in expected_symbols:
assert expected in symbol_names, f"{expected} not found in Person.fs symbols" assert expected in symbol_names, f"{expected} not found in Person.fs symbols"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_find_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None:
"""Test finding references to Calculator functions across files.""" """Test finding references to Calculator functions across files."""
# Find the subtract function in Calculator.fs # Find the subtract function in Calculator.fs
@@ -143,7 +146,7 @@ class TestFSharpLanguageServer:
# The subtract function should be referenced in Program.fs # The subtract function should be referenced in Program.fs
assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference subtract function" assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference subtract function"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_go_to_definition(self, language_server: SolidLanguageServer) -> None: def test_go_to_definition(self, language_server: SolidLanguageServer) -> None:
"""Test go-to-definition functionality.""" """Test go-to-definition functionality."""
# Test going to definition of 'add' function from Program.fs # Test going to definition of 'add' function from Program.fs
@@ -156,7 +159,7 @@ class TestFSharpLanguageServer:
# We should get at least some definitions # We should get at least some definitions
assert len(definitions) >= 0, "Should get definitions (even if empty for complex cases)" assert len(definitions) >= 0, "Should get definitions (even if empty for complex cases)"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_hover_information(self, language_server: SolidLanguageServer) -> None: def test_hover_information(self, language_server: SolidLanguageServer) -> None:
"""Test hover information functionality.""" """Test hover information functionality."""
file_path = os.path.join("Calculator.fs") file_path = os.path.join("Calculator.fs")
@@ -168,7 +171,7 @@ class TestFSharpLanguageServer:
# This is acceptable as it depends on the LSP server's capabilities and timing # This is acceptable as it depends on the LSP server's capabilities and timing
assert hover_info is None or isinstance(hover_info, dict), "Hover info should be None or dict" assert hover_info is None or isinstance(hover_info, dict), "Hover info should be None or dict"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_completion(self, language_server: SolidLanguageServer) -> None: def test_completion(self, language_server: SolidLanguageServer) -> None:
"""Test code completion functionality.""" """Test code completion functionality."""
file_path = os.path.join("Program.fs") file_path = os.path.join("Program.fs")
@@ -197,7 +200,7 @@ class TestFSharpLanguageServer:
assert isinstance(result["value"], list), "Completions should be a list" assert isinstance(result["value"], list), "Completions should be a list"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
@@ -206,7 +209,7 @@ class TestFSharpLanguageServer:
min_count=1, min_count=1,
) )
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.FSHARP], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+20 -20
View File
@@ -5,22 +5,22 @@ import pytest
from serena.symbol import LanguageServerSymbol from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import find_identifier_position, get_repo_path, language_has_verified_implementation_support from test.conftest import find_identifier_position, get_repo_path, ls_has_verified_implementation_support
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.go @pytest.mark.go
class TestGoLanguageServer: class TestGoLanguageServer:
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoStruct"), "DemoStruct 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) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_find_symbol_matches_go_method_by_bare_name(self, language_server: SolidLanguageServer) -> None: 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") symbols = language_server.request_full_symbol_tree(within_relative_path="main.go")
@@ -34,7 +34,7 @@ class TestGoLanguageServer:
assert bare_name_matches, "Expected a Go method to match by bare name" assert bare_name_matches, "Expected a Go method to match by bare name"
assert all(match.name == "Value" for match in bare_name_matches) assert all(match.name == "Value" for match in bare_name_matches)
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("main.go") file_path = os.path.join("main.go")
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
@@ -48,7 +48,7 @@ class TestGoLanguageServer:
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any("main.go" in ref.get("uri", "") for ref in refs), "Expected at least one reference result to point at main.go" assert any("main.go" in ref.get("uri", "") for ref in refs), "Expected at least one reference result to point at main.go"
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_type_var_const_body_includes_leading_keyword(self, language_server: SolidLanguageServer) -> None: def test_type_var_const_body_includes_leading_keyword(self, language_server: SolidLanguageServer) -> None:
""" """
Single ``type``/``var``/``const`` declarations must expose a body and replacement range that Single ``type``/``var``/``const`` declarations must expose a body and replacement range that
@@ -88,11 +88,11 @@ class TestGoLanguageServer:
assert body.startswith(name), f"Expected grouped var {name} body to start with the identifier, got {body[:24]!r}" assert body.startswith(name), f"Expected grouped var {name} body to start with the identifier, got {body[:24]!r}"
assert not body.startswith("var"), f"Grouped var {name} body must not include the 'var' keyword" assert not body.startswith("var"), f"Grouped var {name} body must not include the 'var' keyword"
if language_has_verified_implementation_support(Language.GO): if ls_has_verified_implementation_support(LanguageServerId.GO):
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_find_implementations(self, language_server: SolidLanguageServer) -> None: def test_find_implementations(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.GO) repo_path = get_repo_path(LanguageServerId.GO)
pos = find_identifier_position(repo_path / "main.go", "FormatGreeting") pos = find_identifier_position(repo_path / "main.go", "FormatGreeting")
assert pos is not None, "Could not find Greeter.FormatGreeting in fixture" assert pos is not None, "Could not find Greeter.FormatGreeting in fixture"
@@ -102,9 +102,9 @@ class TestGoLanguageServer:
f"Expected ConsoleGreeter.FormatGreeting in implementations, got: {implementations}" f"Expected ConsoleGreeter.FormatGreeting in implementations, got: {implementations}"
) )
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None: def test_request_implementing_symbols(self, language_server: SolidLanguageServer) -> None:
repo_path = get_repo_path(Language.GO) repo_path = get_repo_path(LanguageServerId.GO)
pos = find_identifier_position(repo_path / "main.go", "FormatGreeting") pos = find_identifier_position(repo_path / "main.go", "FormatGreeting")
assert pos is not None, "Could not find Greeter.FormatGreeting in fixture" assert pos is not None, "Could not find Greeter.FormatGreeting in fixture"
@@ -133,7 +133,7 @@ class TestGoBuildTags:
from test.conftest import get_repo_path from test.conftest import get_repo_path
fixture_path = get_repo_path(Language.GO) fixture_path = get_repo_path(LanguageServerId.GO)
target_path = tmp_path / "test_repo" target_path = tmp_path / "test_repo"
shutil.copytree(fixture_path, target_path) shutil.copytree(fixture_path, target_path)
@@ -145,7 +145,7 @@ class TestGoBuildTags:
repo_path = self._copy_go_fixture(tmp_path) repo_path = self._copy_go_fixture(tmp_path)
with start_ls_context(Language.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls: with start_ls_context(LanguageServerId.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls:
xnotfoo_symbols = ls.request_workspace_symbol("XNotFoo") xnotfoo_symbols = ls.request_workspace_symbol("XNotFoo")
xfoo_symbols = ls.request_workspace_symbol("XFoo") xfoo_symbols = ls.request_workspace_symbol("XFoo")
@@ -162,14 +162,14 @@ class TestGoBuildTags:
repo_path = self._copy_go_fixture(tmp_path) repo_path = self._copy_go_fixture(tmp_path)
ls_settings = { ls_settings = {
Language.GO: { LanguageServerId.GO: {
"gopls_settings": { "gopls_settings": {
"buildFlags": ["-tags=foo"], "buildFlags": ["-tags=foo"],
}, },
}, },
} }
with start_ls_context(Language.GO, repo_path=str(repo_path), ls_specific_settings=ls_settings, solidlsp_dir=tmp_path) as ls: with start_ls_context(LanguageServerId.GO, repo_path=str(repo_path), ls_specific_settings=ls_settings, solidlsp_dir=tmp_path) as ls:
xfoo_symbols = ls.request_workspace_symbol("XFoo") xfoo_symbols = ls.request_workspace_symbol("XFoo")
xnotfoo_symbols = ls.request_workspace_symbol("XNotFoo") xnotfoo_symbols = ls.request_workspace_symbol("XNotFoo")
@@ -188,7 +188,7 @@ class TestGoBuildTags:
repo_path = self._copy_go_fixture(tmp_path) repo_path = self._copy_go_fixture(tmp_path)
ls_settings_foo = { ls_settings_foo = {
Language.GO: { LanguageServerId.GO: {
"gopls_settings": { "gopls_settings": {
"buildFlags": ["-tags=foo"], "buildFlags": ["-tags=foo"],
}, },
@@ -214,7 +214,7 @@ class TestGoBuildTags:
assert ls._document_symbols_cache_is_modified assert ls._document_symbols_cache_is_modified
# Run 1 (default context): populate caches and persist them to disk. # Run 1 (default context): populate caches and persist them to disk.
with start_ls_context(Language.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default: with start_ls_context(LanguageServerId.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default:
_ = ls_default.request_document_symbols(main_go) _ = ls_default.request_document_symbols(main_go)
default_raw_cache_version = ls_default._raw_document_symbols_cache_version() default_raw_cache_version = ls_default._raw_document_symbols_cache_version()
@@ -243,7 +243,7 @@ class TestGoBuildTags:
) )
# Run 2 (default context again): prove that persisted caches are actually loaded and used. # Run 2 (default context again): prove that persisted caches are actually loaded and used.
with start_ls_context(Language.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default_again: with start_ls_context(LanguageServerId.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default_again:
assert ls_default_again.cache_dir == cache_dir assert ls_default_again.cache_dir == cache_dir
_assert_caches_loaded_and_clean(ls_default_again) _assert_caches_loaded_and_clean(ls_default_again)
@@ -256,7 +256,7 @@ class TestGoBuildTags:
# Run 3 (foo context): the same on-disk cache directory exists, but MUST be treated as stale. # Run 3 (foo context): the same on-disk cache directory exists, but MUST be treated as stale.
with start_ls_context( with start_ls_context(
Language.GO, LanguageServerId.GO,
repo_path=str(repo_path), repo_path=str(repo_path),
ls_specific_settings=ls_settings_foo, ls_specific_settings=ls_settings_foo,
solidlsp_dir=tmp_path, solidlsp_dir=tmp_path,
@@ -277,7 +277,7 @@ class TestGoBuildTags:
# A cache miss should repopulate and mark caches modified. # A cache miss should repopulate and mark caches modified.
_assert_caches_modified(ls_foo) _assert_caches_modified(ls_foo)
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.go @pytest.mark.go
class TestGoDiagnostics: class TestGoDiagnostics:
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+3 -3
View File
@@ -5,7 +5,7 @@ import pytest
from serena.constants import SERENA_MANAGED_DIR_NAME from serena.constants import SERENA_MANAGED_DIR_NAME
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@@ -43,13 +43,13 @@ class TestGroovyLanguageServer:
# Create language server directly with Groovy-specific settings # Create language server directly with Groovy-specific settings
repo_path = str(cls.test_repo_path) repo_path = str(cls.test_repo_path)
config = LanguageServerConfig(code_language=Language.GROOVY, ignored_paths=[], trace_lsp_communication=False) config = LanguageServerConfig(ls_id=LanguageServerId.GROOVY, ignored_paths=[], trace_lsp_communication=False)
project_data_path = os.path.join(repo_path, SERENA_MANAGED_DIR_NAME) project_data_path = os.path.join(repo_path, SERENA_MANAGED_DIR_NAME)
solidlsp_settings = SolidLSPSettings( solidlsp_settings = SolidLSPSettings(
solidlsp_dir=str(Path.home() / ".serena"), solidlsp_dir=str(Path.home() / ".serena"),
project_data_path=project_data_path, project_data_path=project_data_path,
ls_specific_settings={Language.GROOVY: groovy_settings}, ls_specific_settings={LanguageServerId.GROOVY: groovy_settings},
) )
cls.language_server = SolidLanguageServer.create(config, repo_path, solidlsp_settings=solidlsp_settings) cls.language_server = SolidLanguageServer.create(config, repo_path, solidlsp_settings=solidlsp_settings)
+15 -13
View File
@@ -16,18 +16,20 @@ Test Repository Structure:
import pytest import pytest
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.conftest import language_tests_enabled from test.conftest import language_server_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
pytestmark = pytest.mark.skipif(not language_tests_enabled(Language.HASKELL), reason="Haskell tests are disabled (HLS not available)") pytestmark = pytest.mark.skipif(
not language_server_tests_enabled(LanguageServerId.HASKELL), reason="Haskell tests are disabled (HLS not available)"
)
@pytest.mark.haskell @pytest.mark.haskell
class TestHaskellLanguageServer: class TestHaskellLanguageServer:
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_calculator_module_symbols(self, language_server: SolidLanguageServer): def test_calculator_module_symbols(self, language_server: SolidLanguageServer):
""" """
Test precise symbol discovery in Calculator.hs. Test precise symbol discovery in Calculator.hs.
@@ -66,7 +68,7 @@ class TestHaskellLanguageServer:
23, 23,
], f"Calculator should be a data type (kind 1, 5, or 23), got kind {calculator_symbol['kind']}" ], f"Calculator should be a data type (kind 1, 5, or 23), got kind {calculator_symbol['kind']}"
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_helper_module_symbols(self, language_server: SolidLanguageServer): def test_helper_module_symbols(self, language_server: SolidLanguageServer):
""" """
Test precise symbol discovery in Helper.hs. Test precise symbol discovery in Helper.hs.
@@ -93,7 +95,7 @@ class TestHaskellLanguageServer:
extra = symbol_names - expected_symbols - {"Helper"} extra = symbol_names - expected_symbols - {"Helper"}
assert not extra, f"Unexpected symbols in Helper.hs: {extra}" assert not extra, f"Unexpected symbols in Helper.hs: {extra}"
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_main_module_imports(self, language_server: SolidLanguageServer): def test_main_module_imports(self, language_server: SolidLanguageServer):
""" """
Test that Main.hs properly references both Calculator and Helper modules. Test that Main.hs properly references both Calculator and Helper modules.
@@ -106,7 +108,7 @@ class TestHaskellLanguageServer:
# Main.hs should have the main function # Main.hs should have the main function
assert "main" in symbol_names, "Main.hs should contain 'main' function" assert "main" in symbol_names, "Main.hs should contain 'main' function"
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_cross_file_references_validateNumber(self, language_server: SolidLanguageServer): def test_cross_file_references_validateNumber(self, language_server: SolidLanguageServer):
""" """
Test cross-file reference tracking for validateNumber function. Test cross-file reference tracking for validateNumber function.
@@ -132,7 +134,7 @@ class TestHaskellLanguageServer:
f"got {len(calculator_refs)} references in Calculator.hs" f"got {len(calculator_refs)} references in Calculator.hs"
) )
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_within_file_references_isNegative(self, language_server: SolidLanguageServer): def test_within_file_references_isNegative(self, language_server: SolidLanguageServer):
""" """
Test within-file reference tracking for isNegative function. Test within-file reference tracking for isNegative function.
@@ -152,7 +154,7 @@ class TestHaskellLanguageServer:
f"All isNegative references should be in Helper.hs, got: {reference_paths}" f"All isNegative references should be in Helper.hs, got: {reference_paths}"
) )
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_function_references_from_main(self, language_server: SolidLanguageServer): def test_function_references_from_main(self, language_server: SolidLanguageServer):
""" """
Test that functions used in Main.hs can be traced back to their definitions. Test that functions used in Main.hs can be traced back to their definitions.
@@ -175,7 +177,7 @@ class TestHaskellLanguageServer:
f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}" f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}"
) )
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer): def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer):
""" """
Test that multiply function usage is tracked within Calculator module. Test that multiply function usage is tracked within Calculator module.
@@ -198,7 +200,7 @@ class TestHaskellLanguageServer:
f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}" f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}"
) )
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_data_type_constructor_references(self, language_server: SolidLanguageServer): def test_data_type_constructor_references(self, language_server: SolidLanguageServer):
""" """
Test that Calculator data type constructor usage is tracked. Test that Calculator data type constructor usage is tracked.
@@ -221,7 +223,7 @@ class TestHaskellLanguageServer:
f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {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) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
@@ -242,7 +244,7 @@ class TestHaskellLanguageServer:
] ]
pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False) pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HASKELL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+16 -16
View File
@@ -3,7 +3,7 @@ import os
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@@ -11,12 +11,12 @@ from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name,
@pytest.mark.haxe @pytest.mark.haxe
class TestHaxeLanguageServer: class TestHaxeLanguageServer:
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer) -> None: def test_ls_is_running(self, language_server: SolidLanguageServer) -> None:
"""Test that the Haxe language server starts successfully.""" """Test that the Haxe language server starts successfully."""
assert language_server.is_running() assert language_server.is_running()
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None: def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main class not found in symbol tree"
@@ -26,7 +26,7 @@ class TestHaxeLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "addNumbers"), "addNumbers method not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "addNumbers"), "addNumbers method not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "formatMessage"), "formatMessage method not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "formatMessage"), "formatMessage method not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None: def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
"""Test that symbol names do not contain unexpected formatting characters.""" """Test that symbol names do not contain unexpected formatting characters."""
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
@@ -40,7 +40,7 @@ class TestHaxeLanguageServer:
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None: def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("src", "Main.hx") file_path = os.path.join("src", "Main.hx")
all_symbols, _ = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() all_symbols, _ = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
@@ -69,7 +69,7 @@ class TestHaxeLanguageServer:
f"Expected all greet references in Main.hx, got {actual_locations}" f"Expected all greet references in Main.hx, got {actual_locations}"
) )
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None: def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
# Test addNumbers which is defined in Helper.hx and used in Main.hx # Test addNumbers which is defined in Helper.hx and used in Main.hx
helper_path = os.path.join("src", "utils", "Helper.hx") helper_path = os.path.join("src", "utils", "Helper.hx")
@@ -103,7 +103,7 @@ class TestHaxeLanguageServer:
main_refs = [loc for loc in actual_locations if loc["uri_suffix"] == "Main.hx"] main_refs = [loc for loc in actual_locations if loc["uri_suffix"] == "Main.hx"]
assert len(main_refs) >= 2, f"Expected at least 2 references in Main.hx (lines 16 and 30), got {main_refs}" assert len(main_refs) >= 2, f"Expected at least 2 references in Main.hx (lines 16 and 30), got {main_refs}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_document_symbols_structure(self, language_server: SolidLanguageServer) -> None: def test_document_symbols_structure(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("src", "Main.hx") file_path = os.path.join("src", "Main.hx")
result = language_server.request_document_symbols(file_path) result = language_server.request_document_symbols(file_path)
@@ -139,14 +139,14 @@ class TestHaxeLanguageServer:
SymbolKind.Property, SymbolKind.Property,
), f"Expected message to be Field/Variable, got {sym.get('kind')}" ), f"Expected message to be Field/Variable, got {sym.get('kind')}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_workspace_symbol(self, language_server: SolidLanguageServer) -> None: def test_workspace_symbol(self, language_server: SolidLanguageServer) -> None:
result = language_server.request_workspace_symbol("Helper") result = language_server.request_workspace_symbol("Helper")
assert result is not None, "Workspace symbol search returned None" assert result is not None, "Workspace symbol search returned None"
assert len(result) > 0, "Workspace symbol search returned no results" assert len(result) > 0, "Workspace symbol search returned no results"
assert any("Helper" in str(s.get("name", "")) for s in result), f"Expected at least one result containing 'Helper', got {result}" assert any("Helper" in str(s.get("name", "")) for s in result), f"Expected at least one result containing 'Helper', got {result}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_go_to_definition_within_file(self, language_server: SolidLanguageServer) -> None: def test_go_to_definition_within_file(self, language_server: SolidLanguageServer) -> None:
"""Go to definition of greet from its call site in Main.hx -- should resolve within the same file.""" """Go to definition of greet from its call site in Main.hx -- should resolve within the same file."""
main_path = os.path.join("src", "Main.hx") main_path = os.path.join("src", "Main.hx")
@@ -161,7 +161,7 @@ class TestHaxeLanguageServer:
definition_lines = [d["range"]["start"]["line"] for d in definitions if "Main.hx" in d.get("uri", d.get("relativePath", ""))] definition_lines = [d["range"]["start"]["line"] for d in definitions if "Main.hx" in d.get("uri", d.get("relativePath", ""))]
assert 22 in definition_lines, f"Expected definition of greet at line 22 in Main.hx, got lines {definition_lines}" assert 22 in definition_lines, f"Expected definition of greet at line 22 in Main.hx, got lines {definition_lines}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_go_to_definition(self, language_server: SolidLanguageServer) -> None: def test_go_to_definition(self, language_server: SolidLanguageServer) -> None:
"""Go to definition of addNumbers from a call site in Main.hx -- should resolve to Helper.hx.""" """Go to definition of addNumbers from a call site in Main.hx -- should resolve to Helper.hx."""
main_path = os.path.join("src", "Main.hx") main_path = os.path.join("src", "Main.hx")
@@ -176,7 +176,7 @@ class TestHaxeLanguageServer:
definition_lines = [d["range"]["start"]["line"] for d in definitions if "Helper.hx" in d.get("uri", d.get("relativePath", ""))] definition_lines = [d["range"]["start"]["line"] for d in definitions if "Helper.hx" in d.get("uri", d.get("relativePath", ""))]
assert 18 in definition_lines, f"Expected definition of addNumbers at line 18 in Helper.hx, got lines {definition_lines}" assert 18 in definition_lines, f"Expected definition of addNumbers at line 18 in Helper.hx, got lines {definition_lines}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_hover(self, language_server: SolidLanguageServer) -> None: def test_hover(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("src", "Main.hx") file_path = os.path.join("src", "Main.hx")
result = language_server.request_document_symbols(file_path) result = language_server.request_document_symbols(file_path)
@@ -190,7 +190,7 @@ class TestHaxeLanguageServer:
hover_str = str(hover) hover_str = str(hover)
assert "String" in hover_str or "greet" in hover_str, f"Expected hover to contain type info, got {hover_str}" assert "String" in hover_str or "greet" in hover_str, f"Expected hover to contain type info, got {hover_str}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_hover_on_class_declaration(self, language_server: SolidLanguageServer) -> None: def test_hover_on_class_declaration(self, language_server: SolidLanguageServer) -> None:
"""Hovering on a class name should return hover info.""" """Hovering on a class name should return hover info."""
file_path = os.path.join("src", "Main.hx") file_path = os.path.join("src", "Main.hx")
@@ -206,7 +206,7 @@ class TestHaxeLanguageServer:
hover_str = str(hover) hover_str = str(hover)
assert "Main" in hover_str, f"Expected hover to contain 'Main', got {hover_str}" assert "Main" in hover_str, f"Expected hover to contain 'Main', got {hover_str}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_rename_symbol(self, language_server: SolidLanguageServer) -> None: def test_rename_symbol(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("src", "Main.hx") file_path = os.path.join("src", "Main.hx")
result = language_server.request_document_symbols(file_path) result = language_server.request_document_symbols(file_path)
@@ -222,7 +222,7 @@ class TestHaxeLanguageServer:
assert "Main.hx" in edits_str, f"Expected rename edits for Main.hx, got {edits}" assert "Main.hx" in edits_str, f"Expected rename edits for Main.hx, got {edits}"
assert "sayHello" in edits_str, f"Expected new name 'sayHello' in rename edits, got {edits}" assert "sayHello" in edits_str, f"Expected new name 'sayHello' in rename edits, got {edits}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_completions(self, language_server: SolidLanguageServer) -> None: def test_completions(self, language_server: SolidLanguageServer) -> None:
"""Request completions after Helper. in calculateResult — should return Helper's static methods.""" """Request completions after Helper. in calculateResult — should return Helper's static methods."""
file_path = os.path.join("src", "Main.hx") file_path = os.path.join("src", "Main.hx")
@@ -233,7 +233,7 @@ class TestHaxeLanguageServer:
completion_texts = [c.get("completionText", c.get("label", "")) for c in completions] completion_texts = [c.get("completionText", c.get("label", "")) for c in completions]
assert "addNumbers" in completion_texts, f"Expected 'addNumbers' in completions after Helper., got {completion_texts[:10]}" assert "addNumbers" in completion_texts, f"Expected 'addNumbers' in completions after Helper., got {completion_texts[:10]}"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_document_overview(self, language_server: SolidLanguageServer) -> None: def test_document_overview(self, language_server: SolidLanguageServer) -> None:
overview = language_server.request_document_overview(os.path.join("src", "Main.hx")) overview = language_server.request_document_overview(os.path.join("src", "Main.hx"))
assert overview, "Document overview returned empty list" assert overview, "Document overview returned empty list"
@@ -248,7 +248,7 @@ class TestHaxeLanguageServer:
assert s.get("name"), "Symbol missing 'name' field" assert s.get("name"), "Symbol missing 'name' field"
assert s.get("kind"), "Symbol missing 'kind' field" assert s.get("kind"), "Symbol missing 'kind' field"
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_rapid_successive_requests(self, language_server: SolidLanguageServer) -> None: def test_rapid_successive_requests(self, language_server: SolidLanguageServer) -> None:
"""Verify that rapid successive requests don't return empty results. """Verify that rapid successive requests don't return empty results.
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.haxe @pytest.mark.haxe
class TestHaxeDiagnostics: class TestHaxeDiagnostics:
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HAXE], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
+12 -12
View File
@@ -10,7 +10,7 @@ from typing import Any
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
@@ -30,19 +30,19 @@ def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, n
class TestHlslSymbols: class TestHlslSymbols:
"""Tests for document symbol extraction.""" """Tests for document symbol extraction."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_find_struct(self, language_server: SolidLanguageServer) -> None: def test_find_struct(self, language_server: SolidLanguageServer) -> None:
"""VertexInput struct should appear in common.hlsl symbols.""" """VertexInput struct should appear in common.hlsl symbols."""
symbol = _find_symbol_by_name(language_server, "common.hlsl", "VertexInput") symbol = _find_symbol_by_name(language_server, "common.hlsl", "VertexInput")
assert symbol is not None, "Expected 'VertexInput' struct in document symbols" assert symbol is not None, "Expected 'VertexInput' struct in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_find_function(self, language_server: SolidLanguageServer) -> None: def test_find_function(self, language_server: SolidLanguageServer) -> None:
"""SafeNormalize function should appear in common.hlsl.""" """SafeNormalize function should appear in common.hlsl."""
symbol = _find_symbol_by_name(language_server, "common.hlsl", "SafeNormalize") symbol = _find_symbol_by_name(language_server, "common.hlsl", "SafeNormalize")
assert symbol is not None, "Expected 'SafeNormalize' function in document symbols" assert symbol is not None, "Expected 'SafeNormalize' function in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_find_cbuffer_members(self, language_server: SolidLanguageServer) -> None: def test_find_cbuffer_members(self, language_server: SolidLanguageServer) -> None:
"""Cbuffer members should appear as variables in compute_test.hlsl. """Cbuffer members should appear as variables in compute_test.hlsl.
@@ -52,13 +52,13 @@ class TestHlslSymbols:
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "TextureSize") symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "TextureSize")
assert symbol is not None, "Expected 'TextureSize' cbuffer member in document symbols" assert symbol is not None, "Expected 'TextureSize' cbuffer member in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_find_compute_kernel(self, language_server: SolidLanguageServer) -> None: def test_find_compute_kernel(self, language_server: SolidLanguageServer) -> None:
"""CSMain kernel should appear in compute_test.hlsl.""" """CSMain kernel should appear in compute_test.hlsl."""
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "CSMain") symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "CSMain")
assert symbol is not None, "Expected 'CSMain' compute kernel in document symbols" assert symbol is not None, "Expected 'CSMain' compute kernel in document symbols"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None: def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
"""Full symbol tree should contain symbols from multiple files.""" """Full symbol tree should contain symbols from multiple files."""
symbols = language_server.request_full_symbol_tree() symbols = language_server.request_full_symbol_tree()
@@ -73,7 +73,7 @@ class TestHlslSymbols:
class TestHlslDefinition: class TestHlslDefinition:
"""Tests for go-to-definition capability.""" """Tests for go-to-definition capability."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
"""Navigating to SafeNormalize call in lighting.hlsl should resolve to common.hlsl. """Navigating to SafeNormalize call in lighting.hlsl should resolve to common.hlsl.
@@ -85,7 +85,7 @@ class TestHlslDefinition:
def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions] def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}" assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_goto_definition_cross_file_remap(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_cross_file_remap(self, language_server: SolidLanguageServer) -> None:
"""Navigating to Remap call in compute_test.hlsl should resolve to common.hlsl. """Navigating to Remap call in compute_test.hlsl should resolve to common.hlsl.
@@ -109,7 +109,7 @@ class TestHlslReferences:
request_references is expected to return an empty list. request_references is expected to return an empty list.
""" """
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_references_not_supported(self, language_server: SolidLanguageServer) -> None: def test_references_not_supported(self, language_server: SolidLanguageServer) -> None:
"""References request should raise because shader-language-server does not support it. """References request should raise because shader-language-server does not support it.
@@ -137,7 +137,7 @@ def _extract_hover_text(hover_info: dict[str, Any]) -> str:
class TestHlslHover: class TestHlslHover:
"""Tests for hover information.""" """Tests for hover information."""
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_hover_on_function(self, language_server: SolidLanguageServer) -> None: def test_hover_on_function(self, language_server: SolidLanguageServer) -> None:
"""Hovering over SafeNormalize definition should return info. """Hovering over SafeNormalize definition should return info.
@@ -150,7 +150,7 @@ class TestHlslHover:
hover_text = _extract_hover_text(hover_info) hover_text = _extract_hover_text(hover_info)
assert len(hover_text) > 0, "Hover text should not be empty" assert len(hover_text) > 0, "Hover text should not be empty"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_hover_on_struct(self, language_server: SolidLanguageServer) -> None: def test_hover_on_struct(self, language_server: SolidLanguageServer) -> None:
"""Hovering over VertexInput should return struct info. """Hovering over VertexInput should return struct info.
@@ -161,7 +161,7 @@ class TestHlslHover:
assert hover_info is not None, "Hover should return information for VertexInput" assert hover_info is not None, "Hover should return information for VertexInput"
assert "contents" in hover_info, "Hover should have contents" assert "contents" in hover_info, "Hover should have contents"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
+2 -2
View File
@@ -1,13 +1,13 @@
import pytest import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import LanguageServerId
from test.solidlsp.util.diagnostics import assert_file_diagnostics from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.hlsl @pytest.mark.hlsl
class TestHlslDiagnostics: class TestHlslDiagnostics:
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True) @pytest.mark.parametrize("language_server", [LanguageServerId.HLSL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics( assert_file_diagnostics(
language_server, language_server,
Loaded 100 of 180 files, more files were not shown because too many files have changed in this diff. Show more