mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-03 13:12:31 +00:00
Refactoring: Rename Language to LanguageServerId
This resolves a long-standing naming issue in Serena & SolidLSP.
This commit is contained in:
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
|
||||
|
||||
### 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
|
||||
class Language(str, Enum):
|
||||
class LanguageServerId(str, Enum):
|
||||
# Existing languages...
|
||||
NEW_LANGUAGE = "new_language"
|
||||
|
||||
@@ -125,20 +125,15 @@ class Language(str, Enum):
|
||||
# Existing cases...
|
||||
case self.NEW_LANGUAGE:
|
||||
return FilenameMatcher(".newlang", ".nl") # File extensions
|
||||
```
|
||||
|
||||
### 2.2 Update Language Server Factory
|
||||
|
||||
In `src/solidlsp/ls.py`, add your language to the `create` method:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def create(cls, config: LanguageServerConfig, repository_root_path: str) -> "SolidLanguageServer":
|
||||
match config.code_language:
|
||||
# Existing cases...
|
||||
case Language.NEW_LANGUAGE:
|
||||
from solidlsp.language_servers.new_language_server import NewLanguageServer
|
||||
return NewLanguageServer(config, repository_root_path)
|
||||
...
|
||||
|
||||
def get_ls_class(self) -> type["SolidLanguageServer"]:
|
||||
match self:
|
||||
# Existing cases...
|
||||
case self.NEW_LANGUAGE:
|
||||
from solidlsp.language_servers.new_language_server import NewLanguageServer
|
||||
return NewLanguageServer
|
||||
```
|
||||
|
||||
## 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.
|
||||
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
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from serena.tools import (
|
||||
GetDiagnosticsForSymbolTool,
|
||||
ReplaceContentTool,
|
||||
)
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
SEPARATOR = "=" * 80
|
||||
REPO_PATH = Path(REPO_ROOT)
|
||||
@@ -40,7 +40,7 @@ def make_agent() -> SerenaAgent:
|
||||
project_root=str(REPO_PATH),
|
||||
project_config=ProjectConfig(
|
||||
project_name="demo_serena_repo",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
ignored_paths=[],
|
||||
excluded_tools=[],
|
||||
read_only=False,
|
||||
|
||||
@@ -12,14 +12,14 @@ from serena.config.serena_config import LanguageBackend, ProjectConfig, Register
|
||||
from serena.constants import REPO_ROOT
|
||||
from serena.project import Project
|
||||
from serena.tools import FindDeclarationTool
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
SEPARATOR = "=" * 80
|
||||
PYTHON_TEST_REPO = Path(REPO_ROOT) / "test" / "resources" / "repos" / "python" / "test_repo"
|
||||
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."""
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.web_dashboard = False
|
||||
@@ -64,7 +64,7 @@ def find_identifier_occurrence_position(file_path: Path, identifier: str, occurr
|
||||
|
||||
|
||||
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:
|
||||
# letting the language server finish startup
|
||||
|
||||
@@ -11,13 +11,13 @@ from serena.config.serena_config import LanguageBackend, ProjectConfig, Register
|
||||
from serena.constants import REPO_ROOT
|
||||
from serena.project import Project
|
||||
from serena.tools import FindImplementationsTool
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
SEPARATOR = "=" * 80
|
||||
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."""
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
serena_config.web_dashboard = False
|
||||
@@ -49,7 +49,7 @@ def print_section(title: str) -> None:
|
||||
|
||||
|
||||
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:
|
||||
# letting the language server finish startup
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
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__":
|
||||
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)
|
||||
fmt = f"%-{max_len + 2}s"
|
||||
for i, l in enumerate(lang_strings):
|
||||
|
||||
+11
-11
@@ -61,7 +61,7 @@ from serena.tools import (
|
||||
from serena.util.gui import system_has_usable_display
|
||||
from serena.util.inspection import iter_subclasses
|
||||
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.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()
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
: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:
|
||||
return self._all_tools[tool_class]
|
||||
@@ -1444,11 +1444,11 @@ class SerenaAgent:
|
||||
tool_class = ToolRegistry().get_tool_class_by_name(tool_name)
|
||||
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()
|
||||
if ls_manager is None:
|
||||
return []
|
||||
return ls_manager.get_active_languages()
|
||||
return ls_manager.get_active_language_server_ids()
|
||||
|
||||
@contextmanager
|
||||
def active_project_context(self, project: Project) -> Iterator[None]:
|
||||
|
||||
+7
-7
@@ -39,7 +39,7 @@ from serena.constants import (
|
||||
from serena.prompt_factory import SerenaPromptFactory
|
||||
from serena.util.cli_util import AutoRegisteringGroup
|
||||
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.util.subprocess_util import subprocess_kwargs
|
||||
|
||||
@@ -701,13 +701,13 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
if os.path.exists(yml_path):
|
||||
raise FileExistsError(f"Project file {yml_path} already exists.")
|
||||
|
||||
languages: list[Language] = []
|
||||
languages: list[LanguageServerId] = []
|
||||
if language:
|
||||
for lang in language:
|
||||
try:
|
||||
languages.append(Language(lang.lower()))
|
||||
languages.append(LanguageServerId(lang.lower()))
|
||||
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}")
|
||||
|
||||
generated_conf = ProjectConfig.autogenerate(
|
||||
@@ -801,13 +801,13 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
|
||||
collected_exceptions: list[Exception] = []
|
||||
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()
|
||||
for i, f in enumerate(tqdm(files, desc="Indexing")):
|
||||
try:
|
||||
ls = ls_mgr.get_language_server(f)
|
||||
ls.request_document_symbols(f)
|
||||
language_file_counts[ls.language] += 1
|
||||
language_file_counts[ls.ls_id] += 1
|
||||
except Exception as e:
|
||||
log.error(f"Failed to index {f}, continuing.")
|
||||
collected_exceptions.append(e)
|
||||
@@ -882,7 +882,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
ls_mgr = proj.create_language_server_manager()
|
||||
try:
|
||||
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)
|
||||
symbols, _ = document_symbols.get_all_symbols_and_roots()
|
||||
if verbose:
|
||||
|
||||
@@ -32,10 +32,10 @@ from serena.constants import (
|
||||
SERENA_FILE_ENCODING,
|
||||
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.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 ..util.class_decorators import singleton
|
||||
@@ -321,7 +321,7 @@ class ProjectConfigAutoGenerationMode(Enum):
|
||||
@dataclass(kw_only=True)
|
||||
class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
project_name: str
|
||||
languages: list[Language]
|
||||
languages: list[LanguageServerId]
|
||||
ignored_paths: list[str] = field(default_factory=list)
|
||||
ls_workspace_folders: list[str] = field(default_factory=lambda: ["."])
|
||||
ls_additional_workspace_folders: list[str] = field(default_factory=list)
|
||||
@@ -356,57 +356,58 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
return ["project_name"]
|
||||
|
||||
@classmethod
|
||||
def _determine_project_languages(cls, project_root: str, interactive: bool, serena_config: "SerenaConfig") -> list[Language]:
|
||||
log.info("Determining programming languages used in the project")
|
||||
def _determine_project_language_servers(
|
||||
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
|
||||
language_priorities = {}
|
||||
for language in Language:
|
||||
# determine language servers to be considered and their priorities
|
||||
ls_priorities = {}
|
||||
for language in LanguageServerId:
|
||||
priority = serena_config.get_ls_priority(language)
|
||||
if priority > 0:
|
||||
language_priorities[language] = priority
|
||||
ls_priorities[language] = priority
|
||||
|
||||
log.debug("Language priorities: %s", language_priorities)
|
||||
language_composition = determine_programming_language_composition(project_root, list(language_priorities.keys()))
|
||||
log.info("Project language composition: %s", language_composition)
|
||||
log.debug("Language server priorities: %s", ls_priorities)
|
||||
ls_composition = compute_language_server_support_composition(project_root, list(ls_priorities.keys()))
|
||||
log.info("Project composition: %s", ls_composition)
|
||||
|
||||
if len(language_composition) == 0:
|
||||
if len(ls_composition) == 0:
|
||||
log.warning(
|
||||
"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 "
|
||||
"when using the LSP backend. You can add languages later via the Serena dashboard "
|
||||
"or by manually editing the project configuration.",
|
||||
project_root,
|
||||
)
|
||||
languages_to_use = []
|
||||
language_servers_to_use = []
|
||||
else:
|
||||
# sort languages by number of files found
|
||||
languages_and_percentages = sorted(
|
||||
language_composition.items(), key=lambda item: (item[1], language_priorities[item[0]]), reverse=True
|
||||
)
|
||||
languages_and_percentages = sorted(ls_composition.items(), key=lambda item: (item[1], ls_priorities[item[0]]), reverse=True)
|
||||
# find the language with the highest percentage and enable it
|
||||
top_language_pair = languages_and_percentages[0]
|
||||
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 len(other_language_pairs) > 0 and interactive:
|
||||
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("Note: Enable only languages you need symbolic retrieval/editing capabilities for.")
|
||||
print(" Additional language servers use resources and some languages may require additional")
|
||||
print(f"Additionally detected {len(other_language_pairs)} other applicable language servers.\n")
|
||||
print("Note: Enable only servers for languages you need symbolic retrieval/editing capabilities for.")
|
||||
print(" Additional language servers use resources and some may require additional")
|
||||
print(" system-level installations/configuration (see Serena documentation).")
|
||||
print("\nWhich additional languages do you want to enable?")
|
||||
for lang, perc in other_language_pairs:
|
||||
enable = ask_yes_no("Enable %s (%.2f%% of source files)?" % (lang.value, perc), default=False)
|
||||
print("\nWhich additional language servers do you want to enable?")
|
||||
for ls_id, perc in other_language_pairs:
|
||||
enable = ask_yes_no("Enable %s (%.2f%% of source files)?" % (ls_id.value, perc), default=False)
|
||||
if enable:
|
||||
languages_to_use.append(lang)
|
||||
language_servers_to_use.append(ls_id)
|
||||
print()
|
||||
|
||||
log.info("Using languages: %s", languages_to_use)
|
||||
return languages_to_use
|
||||
log.info("Using language servers: %s", language_servers_to_use)
|
||||
return language_servers_to_use
|
||||
|
||||
@classmethod
|
||||
def autogenerate(
|
||||
@@ -414,7 +415,7 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
project_root: str | Path,
|
||||
serena_config: "SerenaConfig",
|
||||
project_name: str | None = None,
|
||||
languages: list[Language] | None = None,
|
||||
languages: list[LanguageServerId] | None = None,
|
||||
save_to_disk: bool = True,
|
||||
interactive: bool = False,
|
||||
asynchronous: bool = False,
|
||||
@@ -448,7 +449,7 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
use_asynchronous_language_determination = True
|
||||
languages_to_use = [] # temporarily empty, will be determined in background thread
|
||||
else:
|
||||
determined_languages = cls._determine_project_languages(
|
||||
determined_languages = cls._determine_project_language_servers(
|
||||
str(project_root), interactive=interactive, serena_config=serena_config
|
||||
)
|
||||
languages_to_use = [l.value for l in determined_languages]
|
||||
@@ -477,7 +478,7 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
def async_language_determination():
|
||||
try:
|
||||
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
|
||||
)
|
||||
if save_to_disk:
|
||||
@@ -579,18 +580,18 @@ class ProjectConfig(SharedConfig, ModeSelectionDefinitionWithAddedModes):
|
||||
"""
|
||||
# map languages to list of enum items, checking for errors
|
||||
lang_name_mapping = {"javascript": "typescript"}
|
||||
languages: list[Language] = []
|
||||
languages: list[LanguageServerId] = []
|
||||
for language_str in data["languages"]:
|
||||
orig_language_str = language_str
|
||||
try:
|
||||
language_str = language_str.lower()
|
||||
if language_str in lang_name_mapping:
|
||||
language_str = lang_name_mapping[language_str]
|
||||
language = Language(language_str)
|
||||
language = LanguageServerId(language_str)
|
||||
languages.append(language)
|
||||
except ValueError as e:
|
||||
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
|
||||
|
||||
# Validate activation_command_timeout
|
||||
@@ -1451,18 +1452,18 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
log.info(f"Using language backend from global configuration: {language_backend.name}")
|
||||
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
|
||||
|
||||
:param language: identifies the language server
|
||||
:param ls_id: identifies the language server
|
||||
:return: the integer priority
|
||||
"""
|
||||
if self.ls_priorities is not None:
|
||||
try:
|
||||
configured_value = self.ls_priorities.get(language.value)
|
||||
configured_value = self.ls_priorities.get(ls_id.value)
|
||||
if configured_value is not None:
|
||||
return int(configured_value)
|
||||
except Exception as e:
|
||||
log.error("Error reading language priority for %s: %s. Using default priority.", language.value, e)
|
||||
return language.get_priority()
|
||||
log.error("Error reading language priority for %s: %s. Using default priority.", ls_id.value, e)
|
||||
return ls_id.get_priority()
|
||||
+10
-10
@@ -638,10 +638,10 @@ class SerenaDashboardAPI:
|
||||
self._current_config_overview = self._compute_config_overview().model_dump()
|
||||
|
||||
def _get_available_languages(self) -> ResponseAvailableLanguages:
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
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
|
||||
project = self._agent.get_active_project()
|
||||
@@ -776,24 +776,24 @@ class SerenaDashboardAPI:
|
||||
return {}
|
||||
|
||||
def _add_language(self, request_add_language: RequestAddLanguage) -> None:
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
try:
|
||||
language = Language(request_add_language.language)
|
||||
language = LanguageServerId(request_add_language.language)
|
||||
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
|
||||
self._agent.add_language(language)
|
||||
self._agent.add_language_server(language)
|
||||
|
||||
def _remove_language(self, request_remove_language: RequestRemoveLanguage) -> None:
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
try:
|
||||
language = Language(request_remove_language.language)
|
||||
language = LanguageServerId(request_remove_language.language)
|
||||
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
|
||||
self._agent.remove_language(language)
|
||||
self._agent.remove_language_server(language)
|
||||
|
||||
@staticmethod
|
||||
def _find_first_free_port(start_port: int, host: str) -> int:
|
||||
|
||||
+35
-33
@@ -9,7 +9,7 @@ from sensai.util.logging import LogTime
|
||||
|
||||
from serena.config.serena_config import ProjectConfig, SerenaPaths
|
||||
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.settings import SolidLSPSettings
|
||||
|
||||
@@ -45,17 +45,17 @@ class LanguageServerFactory:
|
||||
self.ls_specific_settings = ls_specific_settings
|
||||
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(
|
||||
workspace_folders=self.project_config.ls_workspace_folders,
|
||||
additional_workspace_folders=self.project_config.ls_additional_workspace_folders,
|
||||
code_language=language,
|
||||
ls_id=ls_id,
|
||||
ignored_paths=self.ignored_patterns,
|
||||
trace_lsp_communication=self.trace_lsp_communication,
|
||||
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(
|
||||
ls_config,
|
||||
self.project_root,
|
||||
@@ -74,7 +74,10 @@ class LanguageServerManager:
|
||||
"""
|
||||
|
||||
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:
|
||||
"""
|
||||
: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()))
|
||||
|
||||
@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.
|
||||
The language servers are started in parallel threads.
|
||||
@@ -106,21 +109,21 @@ class LanguageServerManager:
|
||||
"""
|
||||
|
||||
class StartLSThread(threading.Thread):
|
||||
def __init__(self, language: Language):
|
||||
super().__init__(target=self._start_language_server, name="StartLS:" + language.value)
|
||||
self.language = language
|
||||
def __init__(self, ls_id: LanguageServerId):
|
||||
super().__init__(target=self._start_language_server, name="StartLS:" + ls_id.value)
|
||||
self.ls_id = ls_id
|
||||
self.language_server: SolidLanguageServer | None = None
|
||||
self.exception: Exception | None = None
|
||||
|
||||
def _start_language_server(self) -> None:
|
||||
try:
|
||||
with LogTime(f"Language server startup (language={self.language.value})"):
|
||||
self.language_server = factory.create_language_server(self.language)
|
||||
with LogTime(f"Language server startup (language={self.ls_id.value})"):
|
||||
self.language_server = factory.create_language_server(self.ls_id)
|
||||
self.language_server.start()
|
||||
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:
|
||||
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
|
||||
|
||||
# start language servers in parallel threads
|
||||
@@ -131,14 +134,14 @@ class LanguageServerManager:
|
||||
threads.append(thread)
|
||||
|
||||
# collect language servers and exceptions
|
||||
language_servers: dict[Language, SolidLanguageServer] = {}
|
||||
exceptions: dict[Language, Exception] = {}
|
||||
language_servers: dict[LanguageServerId, SolidLanguageServer] = {}
|
||||
exceptions: dict[LanguageServerId, Exception] = {}
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
if thread.exception is not None:
|
||||
exceptions[thread.language] = thread.exception
|
||||
exceptions[thread.ls_id] = thread.exception
|
||||
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.
|
||||
# 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:
|
||||
if not ls.is_running():
|
||||
log.warning(f"Language server for language {ls.language} is not running; restarting ...")
|
||||
ls = self.restart_language_server(ls.language)
|
||||
log.warning(f"Language server for language {ls.ls_id} is not running; restarting ...")
|
||||
ls = self.restart_language_server(ls.ls_id)
|
||||
return ls
|
||||
|
||||
def _get_suitable_language_server(self, relative_path: str) -> SolidLanguageServer | None:
|
||||
@@ -177,15 +180,15 @@ class LanguageServerManager:
|
||||
ls = self._default_language_server
|
||||
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:
|
||||
raise ValueError(f"No language server factory available to create language server for {language}")
|
||||
language_server = self._language_server_factory.create_language_server(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(ls_id)
|
||||
language_server.start()
|
||||
self._language_servers[language] = language_server
|
||||
self._language_servers[ls_id] = 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.
|
||||
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")
|
||||
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.
|
||||
|
||||
:param language: the language
|
||||
:param factory: the factory to create the language server
|
||||
:param ls_id: the language server to add
|
||||
:return: the newly created language server
|
||||
"""
|
||||
if language in self._language_servers:
|
||||
raise ValueError(f"Language server for language {language.value} already present")
|
||||
return self._create_and_start_language_server(language)
|
||||
if ls_id in self._language_servers:
|
||||
raise ValueError(f"Language server for language {ls_id.value} already present")
|
||||
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.
|
||||
|
||||
@@ -220,7 +222,7 @@ class LanguageServerManager:
|
||||
ls = self._language_servers.pop(language)
|
||||
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.
|
||||
|
||||
@@ -233,7 +235,7 @@ class LanguageServerManager:
|
||||
if ls.is_running():
|
||||
if 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)
|
||||
|
||||
def iter_language_servers(self) -> Iterator[SolidLanguageServer]:
|
||||
|
||||
+20
-20
@@ -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.text_utils import MatchedConsecutiveLines, search_files
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from serena.agent import SerenaAgent
|
||||
@@ -493,7 +493,7 @@ class Project(ToStringMixin):
|
||||
ls_specific_settings=ls_specific_settings,
|
||||
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
|
||||
except Exception as e:
|
||||
self._language_server_manager_init_error = e
|
||||
@@ -512,50 +512,50 @@ class Project(ToStringMixin):
|
||||
raise Exception(msg.build())
|
||||
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
|
||||
language server instance if the LS manager is active.
|
||||
Adds a new language server to the project configuration, starting the corresponding
|
||||
server instance if the LS manager is active.
|
||||
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:
|
||||
log.info(f"Language {language.value} is already present in the project configuration.")
|
||||
if ls_id in self.project_config.language_servers:
|
||||
log.info(f"Language server {ls_id.value} is already present in the project configuration.")
|
||||
return
|
||||
|
||||
# start the language server (if the LS manager is active)
|
||||
if self.language_server_manager is None:
|
||||
log.info("Language server manager is not active; skipping language server startup for the new language.")
|
||||
else:
|
||||
log.info("Adding and starting the language server for new language %s ...", language.value)
|
||||
self.language_server_manager.add_language_server(language)
|
||||
log.info("Adding and starting the language server '%s' ...", ls_id.value)
|
||||
self.language_server_manager.add_language_server(ls_id)
|
||||
|
||||
# update the project configuration
|
||||
self.project_config.languages.append(language)
|
||||
self.project_config.language_servers.append(ls_id)
|
||||
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
|
||||
language server instance if the LS manager is active.
|
||||
Removes a language server from the project configuration, stopping the corresponding
|
||||
server instance if the LS manager is active.
|
||||
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:
|
||||
log.info(f"Language {language.value} is not present in the project configuration.")
|
||||
if ls_id not in self.project_config.language_servers:
|
||||
log.info(f"Language {ls_id.value} is not present in the project configuration.")
|
||||
return
|
||||
# update the project configuration
|
||||
self.project_config.languages.remove(language)
|
||||
self.project_config.language_servers.remove(ls_id)
|
||||
self.save_config()
|
||||
|
||||
# stop the language server (if the LS manager is active)
|
||||
if self.language_server_manager is None:
|
||||
log.info("Language server manager is not active; skipping language server shutdown for the removed language.")
|
||||
else:
|
||||
log.info("Removing and stopping the language server for language %s ...", language.value)
|
||||
self.language_server_manager.remove_language_server(language)
|
||||
log.info("Removing and stopping the language server for language %s ...", ls_id.value)
|
||||
self.language_server_manager.remove_language_server(ls_id)
|
||||
|
||||
def ls_sync_file_system_changes(self) -> int:
|
||||
"""
|
||||
|
||||
@@ -107,7 +107,7 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead):
|
||||
raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ")
|
||||
if not symbol_retriever.can_analyze_file(relative_path):
|
||||
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]
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Callable, Iterator
|
||||
from typing import TypeVar
|
||||
|
||||
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")
|
||||
|
||||
@@ -27,40 +27,42 @@ def iter_subclasses(
|
||||
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
|
||||
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.)
|
||||
from diluting language percentages in repositories where such files dominate.
|
||||
|
||||
:param repo_path: path to the repository to analyze
|
||||
:param languages: the list of languages to consider; if None, use default languages
|
||||
:return: dictionary mapping languages to percentages of recognised source files
|
||||
matching the respective language (denominator = files matched by at least one language)
|
||||
:param ls_ids: the list of language servers to consider; if None, use default (non-experimental ones)
|
||||
:return: dictionary mapping language servers to percentages of recognised source files
|
||||
(denominator = files matched by at least one language server)
|
||||
"""
|
||||
if languages is None:
|
||||
languages = list(Language.iter_all(include_experimental=False))
|
||||
if ls_ids is None:
|
||||
ls_ids = list(LanguageServerId.iter_all(include_experimental=False))
|
||||
|
||||
all_files = find_all_non_ignored_files(repo_path)
|
||||
|
||||
if not all_files:
|
||||
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
|
||||
language_counts: dict[Language, int] = {}
|
||||
ls_file_counts: dict[LanguageServerId, int] = {}
|
||||
recognised_files = 0
|
||||
for file_path in all_files:
|
||||
# Use just the filename for matching, not the full path
|
||||
filename = os.path.basename(file_path)
|
||||
matched_any = False
|
||||
for lang, matcher in matchers.items():
|
||||
for ls_id, matcher in matchers.items():
|
||||
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
|
||||
if matched_any:
|
||||
recognised_files += 1
|
||||
@@ -69,4 +71,4 @@ def determine_programming_language_composition(repo_path: str, languages: list[L
|
||||
return {}
|
||||
|
||||
# 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.language_servers.common import quote_windows_path
|
||||
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_utils import FileUtils
|
||||
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}")
|
||||
|
||||
# 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)
|
||||
default_path = os.path.join(cls.ls_resources_dir(solidlsp_settings), _al_extension_dirname(al_extension_version), "extension")
|
||||
if os.path.exists(default_path):
|
||||
@@ -265,7 +265,7 @@ class ALLanguageServer(SolidLanguageServer):
|
||||
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_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), _al_extension_dirname(al_extension_version))
|
||||
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.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.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -97,13 +97,13 @@ class AngularTypeScriptServer(TypeScriptLanguageServer):
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def get_language_enum_instance(cls) -> Language:
|
||||
return Language.TYPESCRIPT
|
||||
def get_language_server_id(cls) -> LanguageServerId:
|
||||
return LanguageServerId.TYPESCRIPT
|
||||
|
||||
def get_source_fn_matcher(self) -> FilenameMatcher:
|
||||
# Use the Angular matcher so .html template files aren't filtered out of
|
||||
# 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):
|
||||
"""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("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)
|
||||
ts_settings = solidlsp_settings.get_ls_specific_settings(Language.TYPESCRIPT)
|
||||
ng_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.ANGULAR)
|
||||
ts_settings = solidlsp_settings.get_ls_specific_settings(LanguageServerId.TYPESCRIPT)
|
||||
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)
|
||||
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:
|
||||
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")
|
||||
self._ts_server = AngularTypeScriptServer(
|
||||
config=ts_config,
|
||||
@@ -413,7 +413,7 @@ class AngularLanguageServer(SolidLanguageServer):
|
||||
non-fatal: we log and fall back to returning an empty list.
|
||||
"""
|
||||
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")
|
||||
self._html_server = VsCodeHtmlLanguageServer(
|
||||
config=html_config,
|
||||
|
||||
@@ -32,7 +32,7 @@ class BasedPyrightLanguageServer(SolidLanguageServer):
|
||||
config,
|
||||
repository_root_path,
|
||||
None,
|
||||
str(config.code_language),
|
||||
str(config.ls_id),
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from ..ls_config import Language, LanguageServerConfig
|
||||
from ..ls_config import LanguageServerConfig, LanguageServerId
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -73,7 +73,7 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
from overrides import override
|
||||
|
||||
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.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -77,7 +77,7 @@ class ElixirTools(SolidLanguageServer):
|
||||
Setup runtime dependencies for Expert.
|
||||
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)
|
||||
# Check if Elixir is available first
|
||||
elixir_version = cls._get_elixir_version()
|
||||
|
||||
@@ -12,7 +12,7 @@ from sensai.util.logging import LogTime
|
||||
|
||||
from solidlsp import ls_types
|
||||
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.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.
|
||||
"""
|
||||
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_compiler_version = elm_config.get("elm_compiler_version", DEFAULT_ELM_COMPILER_VERSION)
|
||||
npm_registry = elm_config.get("npm_registry")
|
||||
|
||||
@@ -13,7 +13,7 @@ from overrides import override
|
||||
from serena.util.dotnet import DotNETUtil
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
|
||||
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.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
return TCPLanguageServer(
|
||||
connection_info=self._conn_info,
|
||||
language=self.language,
|
||||
ls_id=self.ls_id,
|
||||
determine_log_level=self._determine_log_level,
|
||||
logger=logging_fn,
|
||||
request_timeout=request_timeout,
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
import shlex
|
||||
|
||||
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.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -82,7 +82,7 @@ class GroovyLanguageServer(SolidLanguageServer):
|
||||
ls_jar_options = []
|
||||
|
||||
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", "")
|
||||
if 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.
|
||||
"""
|
||||
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_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}"
|
||||
|
||||
@@ -125,7 +125,7 @@ class GroovyLanguageServer(SolidLanguageServer):
|
||||
java_path = None
|
||||
|
||||
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")
|
||||
if 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
|
||||
"""
|
||||
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")
|
||||
if config_jar_path and os.path.exists(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)
|
||||
file_filter = self._custom_settings.get("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:
|
||||
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
|
||||
# workspace/didChangeConfiguration (its initializationOptions only cover storagePath,
|
||||
# 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}}}}
|
||||
log.info(f"Sending workspace/didChangeConfiguration with file associations: {associations}")
|
||||
self.server.notify.workspace_did_change_configuration(intelephense_config)
|
||||
|
||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
||||
from overrides import override
|
||||
|
||||
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_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -91,7 +91,7 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
]
|
||||
|
||||
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)
|
||||
ls_resource_dir = _lua_ls_install_dir(LuaLanguageServer.ls_resources_dir(solidlsp_settings), lua_ls_version)
|
||||
possible_paths.extend(
|
||||
@@ -118,7 +118,7 @@ class LuaLanguageServer(SolidLanguageServer):
|
||||
@staticmethod
|
||||
def _download_lua_ls(solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""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)
|
||||
system = platform.system()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
@@ -11,7 +11,7 @@ from collections.abc import Iterable
|
||||
from overrides import override
|
||||
|
||||
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_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
|
||||
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:
|
||||
d = json.load(f)
|
||||
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)
|
||||
razor_omnisharp_version = omnisharp_settings.get("razor_omnisharp_version", DEFAULT_RAZOR_OMNISHARP_VERSION)
|
||||
for dependency in d["runtimeDependencies"]:
|
||||
|
||||
@@ -62,7 +62,7 @@ import zipfile
|
||||
|
||||
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection, quote_windows_path
|
||||
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.settings import SolidLSPSettings
|
||||
|
||||
@@ -131,9 +131,9 @@ class PascalLanguageServer(SolidLanguageServer):
|
||||
proc_env: dict[str, str] = {}
|
||||
|
||||
# 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)
|
||||
# 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
|
||||
|
||||
"""
|
||||
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)
|
||||
cls.PASLS_VERSION = 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 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.lsp_protocol_handler.lsp_types import DidChangeConfigurationParams
|
||||
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
|
||||
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))
|
||||
ignore_dirs = perl_settings.get("ignore_dirs", list(_DEFAULT_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
|
||||
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):
|
||||
# Setup runtime dependencies before initializing
|
||||
|
||||
@@ -12,7 +12,7 @@ import subprocess
|
||||
from overrides import override
|
||||
|
||||
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.settings import SolidLSPSettings
|
||||
|
||||
@@ -109,8 +109,6 @@ class PhpactorServer(SolidLanguageServer):
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
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"}
|
||||
if self._custom_settings.get("ignore_vendor", True):
|
||||
|
||||
@@ -17,7 +17,7 @@ from solidlsp.ls import (
|
||||
LSPFileBuffer,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler import lsp_types as protocol_lsp_types
|
||||
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):
|
||||
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"}
|
||||
if self._custom_settings.get("ignore_vendor", True):
|
||||
self._ignored_dirnames.add("vendor")
|
||||
|
||||
@@ -23,7 +23,7 @@ from overrides import override
|
||||
|
||||
from solidlsp import ls_types
|
||||
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_utils import FileUtils
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
@@ -119,7 +119,7 @@ class PowerShellLanguageServer(SolidLanguageServer):
|
||||
@classmethod
|
||||
def _get_pses_path(cls, solidlsp_settings: SolidLSPSettings) -> str | None:
|
||||
"""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)
|
||||
install_dir = _pses_install_dir(cls.ls_resources_dir(solidlsp_settings), pses_version)
|
||||
start_script = install_dir / "PowerShellEditorServices" / "Start-EditorServices.ps1"
|
||||
@@ -132,7 +132,7 @@ class PowerShellLanguageServer(SolidLanguageServer):
|
||||
@classmethod
|
||||
def _download_pses(cls, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""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)
|
||||
download_url = (
|
||||
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
|
||||
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_path = Path(bundled_modules_path) / "PSScriptAnalyzer" / psscriptanalyzer_version
|
||||
|
||||
@@ -51,7 +51,7 @@ class PyreflyLanguageServer(SolidLanguageServer):
|
||||
config,
|
||||
repository_root_path,
|
||||
None,
|
||||
str(config.code_language),
|
||||
str(config.ls_id),
|
||||
solidlsp_settings,
|
||||
)
|
||||
self._ensure_workspace_pyrefly_config()
|
||||
|
||||
@@ -18,7 +18,7 @@ import threading
|
||||
from overrides import override
|
||||
|
||||
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.server import ProcessLaunchInfo
|
||||
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.
|
||||
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)
|
||||
# Detect Ruby version manager environment
|
||||
# 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
|
||||
- log_multi_instance_notice: bool
|
||||
"""
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
defaults: dict[str, object] = {
|
||||
"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:
|
||||
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
|
||||
on_stale_lock_str = scala_settings.get("on_stale_lock", DEFAULT_ON_STALE_LOCK)
|
||||
@@ -120,7 +120,7 @@ class ScalaLanguageServer(SolidLanguageServer):
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=scala_lsp_executable_path, cwd=repository_root_path),
|
||||
config.code_language.value,
|
||||
config.ls_id.value,
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
|
||||
@@ -40,11 +40,6 @@ class Solargraph(SolidLanguageServer):
|
||||
"ruby",
|
||||
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.service_ready_event = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
|
||||
@@ -27,7 +27,7 @@ from solidlsp.ls import (
|
||||
LSPFileBuffer,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig
|
||||
from solidlsp.ls_config import FilenameMatcher, LanguageServerConfig, LanguageServerId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -100,14 +100,14 @@ class SvelteTypeScriptServer(TypeScriptLanguageServer):
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def get_language_enum_instance(cls) -> Language:
|
||||
def get_language_server_id(cls) -> LanguageServerId:
|
||||
"""Return TYPESCRIPT; companion uses the TypeScript LS infrastructure."""
|
||||
return Language.TYPESCRIPT
|
||||
return LanguageServerId.TYPESCRIPT
|
||||
|
||||
@override
|
||||
def get_source_fn_matcher(self) -> FilenameMatcher:
|
||||
# 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
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
@@ -273,7 +273,7 @@ class SvelteLanguageServer(SolidLanguageServer):
|
||||
|
||||
@override
|
||||
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)
|
||||
|
||||
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:
|
||||
""":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(
|
||||
"indexing_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."""
|
||||
try:
|
||||
ts_config = LanguageServerConfig(
|
||||
code_language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
trace_lsp_communication=False,
|
||||
)
|
||||
log.info("Creating companion SvelteTypeScriptServer")
|
||||
|
||||
@@ -5,7 +5,7 @@ import shutil
|
||||
from overrides import override
|
||||
|
||||
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.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -124,7 +124,7 @@ class TerraformLS(SolidLanguageServer):
|
||||
Downloads and installs terraform-ls if not already present.
|
||||
"""
|
||||
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)
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
deps = RuntimeDependencyCollection(
|
||||
|
||||
@@ -34,7 +34,7 @@ class TyLanguageServer(SolidLanguageServer):
|
||||
config,
|
||||
repository_root_path,
|
||||
None,
|
||||
str(config.code_language),
|
||||
str(config.ls_id),
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import threading
|
||||
from overrides import override
|
||||
|
||||
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.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -87,7 +87,7 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
PlatformId.WIN_arm64,
|
||||
]
|
||||
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)
|
||||
npm_registry = vts_config.get("npm_registry")
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from solidlsp.language_servers.typescript_language_server import (
|
||||
prefer_non_node_modules_definition,
|
||||
)
|
||||
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_types import Location
|
||||
from solidlsp.ls_utils import PathUtils
|
||||
@@ -39,19 +39,19 @@ class VueTypeScriptServer(TypeScriptLanguageServer):
|
||||
|
||||
@classmethod
|
||||
@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.
|
||||
|
||||
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 TypeScript language server infrastructure.
|
||||
"""
|
||||
return Language.TYPESCRIPT
|
||||
return LanguageServerId.TYPESCRIPT
|
||||
|
||||
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,
|
||||
# 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):
|
||||
"""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."
|
||||
|
||||
# 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_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")
|
||||
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")
|
||||
|
||||
ts_config = LanguageServerConfig(
|
||||
code_language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
trace_lsp_communication=False,
|
||||
)
|
||||
|
||||
|
||||
+13
-13
@@ -31,7 +31,7 @@ from solidlsp.dependency_provider import (
|
||||
LanguageServerDependencyProviderUvx,
|
||||
)
|
||||
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_process import LanguageServerInterface, StdioLanguageServer
|
||||
from solidlsp.ls_types import UnifiedSymbolInformation
|
||||
@@ -383,8 +383,8 @@ class SolidLanguageServer(ABC):
|
||||
return logging.INFO
|
||||
|
||||
@classmethod
|
||||
def get_language_enum_instance(cls) -> Language:
|
||||
return Language.from_ls_class(cls)
|
||||
def get_language_server_id(cls) -> LanguageServerId:
|
||||
return LanguageServerId.from_ls_class(cls)
|
||||
|
||||
@classmethod
|
||||
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
|
||||
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
|
||||
# (which differs from the signature of the base class constructor).
|
||||
ls = ls_class(config, repository_root_path, solidlsp_settings)
|
||||
@@ -481,13 +481,13 @@ class SolidLanguageServer(ABC):
|
||||
"""
|
||||
self.config = config
|
||||
self._solidlsp_settings = solidlsp_settings
|
||||
lang = self.get_language_enum_instance()
|
||||
self._custom_settings = solidlsp_settings.get_ls_specific_settings(lang)
|
||||
ls_id = self.get_language_server_id()
|
||||
self._custom_settings = solidlsp_settings.get_ls_specific_settings(ls_id)
|
||||
"""
|
||||
the (user-provided) language server-specific 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.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.
|
||||
"""
|
||||
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
|
||||
# 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
|
||||
# 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_generation_by_uri: dict[str, int] = {}
|
||||
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}")
|
||||
return StdioLanguageServer(
|
||||
process_launch_info,
|
||||
language=self.language,
|
||||
ls_id=self.ls_id,
|
||||
determine_log_level=self._determine_log_level,
|
||||
logger=logging_fn,
|
||||
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
|
||||
"""
|
||||
# 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:
|
||||
"""
|
||||
@@ -3131,7 +3131,7 @@ class SolidLanguageServer(ABC):
|
||||
|
||||
: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._start_server()
|
||||
return self
|
||||
|
||||
@@ -87,7 +87,7 @@ class FilenameMatcher:
|
||||
return False
|
||||
|
||||
|
||||
class Language(str, Enum):
|
||||
class LanguageServerId(str, Enum):
|
||||
"""
|
||||
Enumeration of language servers supported by SolidLSP.
|
||||
"""
|
||||
@@ -895,7 +895,7 @@ class LanguageServerConfig:
|
||||
Configuration parameters for a language server instance
|
||||
"""
|
||||
|
||||
code_language: Language
|
||||
ls_id: LanguageServerId
|
||||
"""
|
||||
defines the language server to use
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This module contains the exceptions raised by the framework.
|
||||
"""
|
||||
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
|
||||
class SolidLSPException(Exception):
|
||||
@@ -28,14 +28,14 @@ class SolidLSPException(Exception):
|
||||
|
||||
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
|
||||
"""
|
||||
from .ls_process import LanguageServerTerminatedException
|
||||
|
||||
if isinstance(self.cause, LanguageServerTerminatedException):
|
||||
return self.cause.language
|
||||
return self.cause.ls_id
|
||||
return None
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
+24
-24
@@ -14,7 +14,7 @@ from typing import IO, Any, AnyStr, cast
|
||||
|
||||
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_request import LanguageServerRequest
|
||||
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.
|
||||
"""
|
||||
|
||||
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)
|
||||
self.message = message
|
||||
self.language = language
|
||||
self.ls_id = ls_id
|
||||
self.cause = cause
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -116,18 +116,18 @@ class LanguageServerInterface(ABC):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
language: Language,
|
||||
ls_id: LanguageServerId,
|
||||
determine_log_level: Callable[[str], int],
|
||||
logger: Callable[[str, str, StringDict | str], None] | None = None,
|
||||
request_timeout: float | 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 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.
|
||||
"""
|
||||
self.language = language
|
||||
self.ls_id = ls_id
|
||||
self._determine_log_level = determine_log_level
|
||||
self.send = LanguageServerRequest(self)
|
||||
"""
|
||||
@@ -486,7 +486,7 @@ class StdioLanguageServer(LanguageServerInterface):
|
||||
def __init__(
|
||||
self,
|
||||
process_launch_info: ProcessLaunchInfo,
|
||||
language: Language,
|
||||
ls_id: LanguageServerId,
|
||||
determine_log_level: Callable[[str], int],
|
||||
logger: Callable[[str, str, StringDict | str], None] | None = None,
|
||||
start_independent_lsp_process: bool = True,
|
||||
@@ -494,13 +494,13 @@ class StdioLanguageServer(LanguageServerInterface):
|
||||
) -> None:
|
||||
"""
|
||||
: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 logger: the trace logger function
|
||||
: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.
|
||||
"""
|
||||
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: subprocess.Popen[bytes] | None = None
|
||||
@@ -547,12 +547,12 @@ class StdioLanguageServer(LanguageServerInterface):
|
||||
# start threads to read stdout and stderr of the process
|
||||
threading.Thread(
|
||||
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,
|
||||
).start()
|
||||
threading.Thread(
|
||||
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,
|
||||
).start()
|
||||
|
||||
@@ -571,7 +571,7 @@ class StdioLanguageServer(LanguageServerInterface):
|
||||
# Ignore errors here, we are proceeding to terminate anyway.
|
||||
# terminate the process
|
||||
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:
|
||||
self.process = None
|
||||
@@ -594,7 +594,7 @@ class StdioLanguageServer(LanguageServerInterface):
|
||||
if process.poll() is not None:
|
||||
raise LanguageServerTerminatedException(
|
||||
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
|
||||
time.sleep(0.01)
|
||||
@@ -631,15 +631,15 @@ class StdioLanguageServer(LanguageServerInterface):
|
||||
except LanguageServerTerminatedException as e:
|
||||
exception = 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:
|
||||
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")
|
||||
if not self._is_stopping:
|
||||
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))
|
||||
self._cancel_pending_requests(exception)
|
||||
|
||||
@@ -704,12 +704,12 @@ class TCPLanguageServer(LanguageServerInterface):
|
||||
def __init__(
|
||||
self,
|
||||
connection_info: TCPConnectionInfo,
|
||||
language: Language,
|
||||
ls_id: LanguageServerId,
|
||||
determine_log_level: Callable[[str], int],
|
||||
logger: Callable[[str, str, StringDict | str], None] | None = None,
|
||||
request_timeout: float | 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._sock: socket.socket | None = None
|
||||
self._file: Any = None # socket.makefile("rb") - buffered reader
|
||||
@@ -747,7 +747,7 @@ class TCPLanguageServer(LanguageServerInterface):
|
||||
|
||||
threading.Thread(
|
||||
target=self._read_loop,
|
||||
name=f"LSP-tcp-reader:{self.language.value}",
|
||||
name=f"LSP-tcp-reader:{self.ls_id.value}",
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
@@ -788,7 +788,7 @@ class TCPLanguageServer(LanguageServerInterface):
|
||||
log.error("Failed to write to TCP language server: %s", e)
|
||||
self._sock = 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:
|
||||
"""Read Content-Length-framed LSP messages from the TCP socket and dispatch them."""
|
||||
@@ -802,7 +802,7 @@ class TCPLanguageServer(LanguageServerInterface):
|
||||
line = f.readline()
|
||||
except OSError as exc:
|
||||
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
|
||||
if not line:
|
||||
break
|
||||
@@ -824,17 +824,17 @@ class TCPLanguageServer(LanguageServerInterface):
|
||||
body = f.read(num_bytes)
|
||||
except OSError as exc:
|
||||
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
|
||||
if len(body) < num_bytes:
|
||||
break
|
||||
self._handle_body(body)
|
||||
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")
|
||||
if not self._is_stopping:
|
||||
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))
|
||||
self._cancel_pending_requests(exception)
|
||||
# Clear the socket so is_running() returns False, allowing _ensure_functional_ls
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any
|
||||
from sensai.util.string import ToStringMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -34,7 +34,7 @@ class SolidLSPSettings:
|
||||
For instance, if this is "/home/user/myproject/.solidlsp",
|
||||
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.
|
||||
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")
|
||||
|
||||
class CustomLSSettings(ToStringMixin):
|
||||
"""
|
||||
Represents custom (user-specified) settings for a specific language server.
|
||||
"""
|
||||
|
||||
def __init__(self, settings: dict[str, Any] | None) -> None:
|
||||
self.settings = settings or {}
|
||||
|
||||
@@ -69,11 +73,11 @@ class SolidLSPSettings:
|
||||
value = default_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.
|
||||
:return: A dictionary of settings for the language server.
|
||||
:param ls_id: the language server identifier for which to retrieve settings
|
||||
: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
@@ -19,7 +19,7 @@ from serena.constants import SERENA_MANAGED_DIR_NAME
|
||||
from serena.project import Project
|
||||
from serena.util.file_system import GitignoreParser
|
||||
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.clojure import is_clojure_cli_available
|
||||
@@ -39,47 +39,47 @@ def resources_dir() -> Path:
|
||||
|
||||
|
||||
class LanguageParamRequest:
|
||||
param: Language
|
||||
param: LanguageServerId
|
||||
|
||||
|
||||
_LANGUAGE_REPO_ALIASES: dict[Language, Language] = {
|
||||
Language.CPP_CCLS: Language.CPP,
|
||||
Language.PHP_PHPACTOR: Language.PHP,
|
||||
Language.PHP_PHPANTOM: Language.PHP,
|
||||
Language.PYTHON_JEDI: Language.PYTHON,
|
||||
Language.PYTHON_BASEDPYRIGHT: Language.PYTHON,
|
||||
Language.PYTHON_TY: Language.PYTHON,
|
||||
Language.RUBY_SOLARGRAPH: Language.RUBY,
|
||||
Language.PYTHON_TY: Language.PYTHON,
|
||||
Language.PYTHON_PYREFLY: Language.PYTHON,
|
||||
_LANGUAGE_REPO_ALIASES: dict[LanguageServerId, LanguageServerId] = {
|
||||
LanguageServerId.CPP_CCLS: LanguageServerId.CPP,
|
||||
LanguageServerId.PHP_PHPACTOR: LanguageServerId.PHP,
|
||||
LanguageServerId.PHP_PHPANTOM: LanguageServerId.PHP,
|
||||
LanguageServerId.PYTHON_JEDI: LanguageServerId.PYTHON,
|
||||
LanguageServerId.PYTHON_BASEDPYRIGHT: LanguageServerId.PYTHON,
|
||||
LanguageServerId.PYTHON_TY: LanguageServerId.PYTHON,
|
||||
LanguageServerId.RUBY_SOLARGRAPH: LanguageServerId.RUBY,
|
||||
LanguageServerId.PYTHON_TY: LanguageServerId.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)
|
||||
return Path(__file__).parent / "resources" / "repos" / repo_language / "test_repo"
|
||||
|
||||
|
||||
def _create_ls(
|
||||
language: Language,
|
||||
ls_id: LanguageServerId,
|
||||
repo_path: str | None = None,
|
||||
ignored_paths: list[str] | None = None,
|
||||
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,
|
||||
additional_workspace_folders: list[str] | None = None,
|
||||
solidlsp_dir: Path | None = None,
|
||||
) -> SolidLanguageServer:
|
||||
ignored_paths = ignored_paths or []
|
||||
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))
|
||||
for spec in gitignore_parser.get_ignore_specs():
|
||||
ignored_paths.extend(spec.patterns)
|
||||
config = LanguageServerConfig(
|
||||
code_language=language,
|
||||
ls_id=ls_id,
|
||||
ignored_paths=ignored_paths,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
workspace_folders=workspace_folders or ["."],
|
||||
@@ -100,17 +100,17 @@ def _create_ls(
|
||||
|
||||
@contextmanager
|
||||
def start_ls_context(
|
||||
language: Language,
|
||||
ls_id: LanguageServerId,
|
||||
repo_path: str | None = None,
|
||||
ignored_paths: list[str] | None = None,
|
||||
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,
|
||||
additional_workspace_folders: list[str] | None = None,
|
||||
solidlsp_dir: Path | None = None,
|
||||
) -> Iterator[SolidLanguageServer]:
|
||||
ls = _create_ls(
|
||||
language,
|
||||
ls_id,
|
||||
repo_path,
|
||||
ignored_paths,
|
||||
trace_lsp_communication,
|
||||
@@ -119,14 +119,14 @@ def start_ls_context(
|
||||
additional_workspace_folders,
|
||||
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():
|
||||
yield ls
|
||||
|
||||
|
||||
@contextmanager
|
||||
def start_default_ls_context(language: Language) -> Iterator[SolidLanguageServer]:
|
||||
with start_ls_context(language) as ls:
|
||||
def start_default_ls_context(ls_id: LanguageServerId) -> Iterator[SolidLanguageServer]:
|
||||
with start_ls_context(ls_id) as ls:
|
||||
yield ls
|
||||
|
||||
|
||||
@@ -134,8 +134,8 @@ def create_default_serena_config():
|
||||
return SerenaConfig().with_headless_mode_overrides()
|
||||
|
||||
|
||||
def _create_default_project(language: Language, repo_root_override: str | None = None) -> Project:
|
||||
repo_path = str(get_repo_path(language)) if repo_root_override is None else repo_root_override
|
||||
def _create_default_project(ls_id: LanguageServerId, repo_root_override: str | None = None) -> Project:
|
||||
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())
|
||||
|
||||
|
||||
@@ -193,9 +193,9 @@ def language_server(request: LanguageParamRequest):
|
||||
|
||||
|
||||
@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."""
|
||||
project = _create_default_project(language, repo_root_override)
|
||||
project = _create_default_project(ls_id, repo_root_override)
|
||||
try:
|
||||
yield project
|
||||
finally:
|
||||
@@ -233,16 +233,16 @@ def project(request: LanguageParamRequest, repo_root_override: str | None = None
|
||||
|
||||
|
||||
@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."""
|
||||
with project_context(language, repo_root_override) as project:
|
||||
with project_context(ls_id, repo_root_override) as project:
|
||||
project.create_language_server_manager()
|
||||
yield project
|
||||
|
||||
|
||||
@contextmanager
|
||||
def agent_for_project_context(language: Language, 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
|
||||
def agent_for_project_context(ls_id: LanguageServerId, repo_root_override: str | None = None) -> Iterator[SerenaAgent]:
|
||||
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())
|
||||
|
||||
# wait for agent to be ready
|
||||
@@ -273,50 +273,50 @@ is_macos = platform.system() == "Darwin"
|
||||
is_linux = platform.system() == "Linux"
|
||||
|
||||
|
||||
_LANGUAGE_PYTEST_MARKERS: dict[Language, list[MarkDecorator | Mark]] = {
|
||||
Language.ADA: [pytest.mark.ada],
|
||||
Language.CLOJURE: [pytest.mark.clojure],
|
||||
Language.CPP: [pytest.mark.cpp],
|
||||
Language.CPP_CCLS: [pytest.mark.cpp],
|
||||
Language.CUE: [pytest.mark.cue],
|
||||
Language.CSHARP: [pytest.mark.csharp],
|
||||
Language.FSHARP: [pytest.mark.fsharp],
|
||||
Language.GO: [pytest.mark.go],
|
||||
Language.HAXE: [pytest.mark.haxe],
|
||||
Language.JAVA: [pytest.mark.java],
|
||||
Language.KOTLIN: [pytest.mark.kotlin],
|
||||
Language.LEAN4: [pytest.mark.lean4],
|
||||
Language.LATEX: [pytest.mark.latex],
|
||||
Language.MSL: [pytest.mark.msl],
|
||||
Language.PHP: [pytest.mark.php],
|
||||
Language.PHP_PHPACTOR: [pytest.mark.php],
|
||||
Language.PHP_PHPANTOM: [pytest.mark.php],
|
||||
Language.POWERSHELL: [pytest.mark.powershell],
|
||||
Language.PYTHON: [pytest.mark.python],
|
||||
Language.PYTHON_JEDI: [pytest.mark.python],
|
||||
Language.PYTHON_TY: [pytest.mark.python],
|
||||
Language.PYTHON_PYREFLY: [pytest.mark.python],
|
||||
Language.PYTHON_BASEDPYRIGHT: [pytest.mark.python],
|
||||
Language.RUST: [pytest.mark.rust],
|
||||
Language.TYPESCRIPT: [pytest.mark.typescript],
|
||||
Language.BSL: [pytest.mark.bsl],
|
||||
Language.SVELTE: [pytest.mark.svelte],
|
||||
Language.ANGULAR: [pytest.mark.angular],
|
||||
Language.HTML: [pytest.mark.html],
|
||||
Language.SCSS: [pytest.mark.scss],
|
||||
_LANGUAGE_PYTEST_MARKERS: dict[LanguageServerId, list[MarkDecorator | Mark]] = {
|
||||
LanguageServerId.ADA: [pytest.mark.ada],
|
||||
LanguageServerId.CLOJURE: [pytest.mark.clojure],
|
||||
LanguageServerId.CPP: [pytest.mark.cpp],
|
||||
LanguageServerId.CPP_CCLS: [pytest.mark.cpp],
|
||||
LanguageServerId.CUE: [pytest.mark.cue],
|
||||
LanguageServerId.CSHARP: [pytest.mark.csharp],
|
||||
LanguageServerId.FSHARP: [pytest.mark.fsharp],
|
||||
LanguageServerId.GO: [pytest.mark.go],
|
||||
LanguageServerId.HAXE: [pytest.mark.haxe],
|
||||
LanguageServerId.JAVA: [pytest.mark.java],
|
||||
LanguageServerId.KOTLIN: [pytest.mark.kotlin],
|
||||
LanguageServerId.LEAN4: [pytest.mark.lean4],
|
||||
LanguageServerId.LATEX: [pytest.mark.latex],
|
||||
LanguageServerId.MSL: [pytest.mark.msl],
|
||||
LanguageServerId.PHP: [pytest.mark.php],
|
||||
LanguageServerId.PHP_PHPACTOR: [pytest.mark.php],
|
||||
LanguageServerId.PHP_PHPANTOM: [pytest.mark.php],
|
||||
LanguageServerId.POWERSHELL: [pytest.mark.powershell],
|
||||
LanguageServerId.PYTHON: [pytest.mark.python],
|
||||
LanguageServerId.PYTHON_JEDI: [pytest.mark.python],
|
||||
LanguageServerId.PYTHON_TY: [pytest.mark.python],
|
||||
LanguageServerId.PYTHON_PYREFLY: [pytest.mark.python],
|
||||
LanguageServerId.PYTHON_BASEDPYRIGHT: [pytest.mark.python],
|
||||
LanguageServerId.RUST: [pytest.mark.rust],
|
||||
LanguageServerId.TYPESCRIPT: [pytest.mark.typescript],
|
||||
LanguageServerId.BSL: [pytest.mark.bsl],
|
||||
LanguageServerId.SVELTE: [pytest.mark.svelte],
|
||||
LanguageServerId.ANGULAR: [pytest.mark.angular],
|
||||
LanguageServerId.HTML: [pytest.mark.html],
|
||||
LanguageServerId.SCSS: [pytest.mark.scss],
|
||||
}
|
||||
|
||||
|
||||
def get_pytest_markers(language: Language) -> list[MarkDecorator | Mark]:
|
||||
"""Pytest markers for a language.
|
||||
def get_pytest_markers(ls_id: LanguageServerId) -> list[MarkDecorator | Mark]:
|
||||
"""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
|
||||
place (``_determine_disabled_languages``) instead of being duplicated per marker or per test file.
|
||||
"""
|
||||
return [
|
||||
*_LANGUAGE_PYTEST_MARKERS[language],
|
||||
pytest.mark.skipif(not language_tests_enabled(language), reason=f"{language.value} tests are disabled in this environment"),
|
||||
*_LANGUAGE_PYTEST_MARKERS[ls_id],
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
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.
|
||||
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) ===
|
||||
result.append(Language.BSL) # 1C:Enterprise; niche and the tests are slow and flaky
|
||||
result.append(Language.FSHARP) # F# language server is currently unreliable
|
||||
result.append(LanguageServerId.BSL) # 1C:Enterprise; niche and the tests are slow and flaky
|
||||
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 ===
|
||||
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:
|
||||
result.append(Language.REGO)
|
||||
result.append(LanguageServerId.REGO)
|
||||
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
|
||||
# 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.
|
||||
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) ===
|
||||
# 3a. Platform precondition: these language servers have no native Windows support.
|
||||
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:
|
||||
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).
|
||||
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
|
||||
result.append(Language.CPP_CCLS)
|
||||
result.append(LanguageServerId.CPP_CCLS)
|
||||
if _sh.which("php") is None:
|
||||
result.append(Language.PHP_PHPACTOR)
|
||||
result.append(Language.PHP_PHPANTOM)
|
||||
result.append(LanguageServerId.PHP_PHPACTOR)
|
||||
result.append(LanguageServerId.PHP_PHPANTOM)
|
||||
if not is_clojure_cli_available():
|
||||
result.append(Language.CLOJURE)
|
||||
result.append(LanguageServerId.CLOJURE)
|
||||
if _sh.which("verible-verilog-ls") is None:
|
||||
result.append(Language.SYSTEMVERILOG)
|
||||
result.append(LanguageServerId.SYSTEMVERILOG)
|
||||
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
|
||||
result.append(Language.ERLANG)
|
||||
result.append(LanguageServerId.ERLANG)
|
||||
if EXPERT_UNAVAILABLE: # Elixir not installed -- see test/solidlsp/elixir
|
||||
result.append(Language.ELIXIR)
|
||||
result.append(LanguageServerId.ELIXIR)
|
||||
if _sh.which("lean") is None:
|
||||
result.append(Language.LEAN4)
|
||||
result.append(LanguageServerId.LEAN4)
|
||||
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
|
||||
result.append(Language.JULIA)
|
||||
result.append(LanguageServerId.JULIA)
|
||||
if _sh.which("nixd") is None:
|
||||
result.append(Language.NIX)
|
||||
result.append(LanguageServerId.NIX)
|
||||
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
|
||||
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
|
||||
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
|
||||
result.append(Language.PERL)
|
||||
result.append(LanguageServerId.PERL)
|
||||
|
||||
# === 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) ===
|
||||
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
|
||||
|
||||
|
||||
_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 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()
|
||||
|
||||
|
||||
def languages_supporting_implementation(*languages: Language) -> list[Language]:
|
||||
return [language for language in languages if language_supports_implementation(language)]
|
||||
def language_servers_supporting_implementation(*languages: LanguageServerId) -> list[LanguageServerId]:
|
||||
return [language for language in languages if ls_supports_implementation(language)]
|
||||
|
||||
|
||||
_VERIFIED_IMPLEMENTATION_LANGUAGES = {
|
||||
Language.ANGULAR,
|
||||
Language.CSHARP,
|
||||
Language.GO,
|
||||
Language.JAVA,
|
||||
Language.RUST,
|
||||
Language.TYPESCRIPT,
|
||||
LanguageServerId.ANGULAR,
|
||||
LanguageServerId.CSHARP,
|
||||
LanguageServerId.GO,
|
||||
LanguageServerId.JAVA,
|
||||
LanguageServerId.RUST,
|
||||
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
|
||||
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:
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig
|
||||
from serena.project import Project
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
|
||||
def _create_test_project(
|
||||
@@ -16,7 +16,7 @@ def _create_test_project(
|
||||
"""Helper to create a Project with the given ignored paths configuration."""
|
||||
config = ProjectConfig(
|
||||
project_name="test_project",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
ignored_paths=project_ignored_paths or [],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
@@ -145,7 +145,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
"""RegisteredProject.get_project_instance() passes global_ignored_paths to Project."""
|
||||
config = ProjectConfig(
|
||||
project_name="test_project",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
ignored_paths=[],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
@@ -161,7 +161,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
"""RegisteredProject without global_ignored_paths defaults to empty."""
|
||||
config = ProjectConfig(
|
||||
project_name="test_project",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
ignored_paths=[],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
@@ -193,7 +193,7 @@ class TestRegisteredProjectGlobalIgnoredPaths:
|
||||
"""RegisteredProject.from_project_instance() threads global_ignored_paths to Project."""
|
||||
config = ProjectConfig(
|
||||
project_name="test_project",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
ignored_paths=[],
|
||||
ignore_all_files_in_gitignore=False,
|
||||
)
|
||||
@@ -233,7 +233,7 @@ class TestGlobalIgnoredPathsWithGitignore:
|
||||
"""Global patterns, project patterns, and .gitignore patterns are all applied together."""
|
||||
config = ProjectConfig(
|
||||
project_name="test_project",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
ignored_paths=["build"],
|
||||
ignore_all_files_in_gitignore=True,
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ from serena.config.serena_config import (
|
||||
)
|
||||
from serena.constants import PROJECT_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME
|
||||
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
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestProjectConfigAutogenerate:
|
||||
|
||||
# Verify the configuration
|
||||
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):
|
||||
"""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")
|
||||
|
||||
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
|
||||
config = ProjectConfig.autogenerate(self.project_path, serena_config, save_to_disk=False)
|
||||
|
||||
# Verify the configuration
|
||||
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):
|
||||
"""Test successful autogeneration with JavaScript source files."""
|
||||
@@ -88,7 +88,7 @@ class TestProjectConfigAutogenerate:
|
||||
# Run autogenerate - should pick Python as dominant
|
||||
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):
|
||||
"""Test autogeneration picks dominant language when multiple are present."""
|
||||
@@ -100,7 +100,7 @@ class TestProjectConfigAutogenerate:
|
||||
# Run autogenerate - should pick Python as dominant
|
||||
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):
|
||||
"""Test that autogenerate can save the configuration to disk."""
|
||||
@@ -116,7 +116,7 @@ class TestProjectConfigAutogenerate:
|
||||
assert config_path.exists()
|
||||
|
||||
# Verify the content
|
||||
assert config.languages == [Language.GO]
|
||||
assert config.languages == [LanguageServerId.GO]
|
||||
|
||||
def test_autogenerate_nonexistent_path(self):
|
||||
"""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)
|
||||
|
||||
assert config.project_name == custom_name
|
||||
assert config.languages == [Language.TYPESCRIPT]
|
||||
assert config.languages == [LanguageServerId.TYPESCRIPT]
|
||||
|
||||
|
||||
class TestProjectConfig:
|
||||
@@ -168,14 +168,14 @@ class TestProjectConfigLanguageBackend:
|
||||
def test_language_backend_defaults_to_none(self):
|
||||
config = ProjectConfig(
|
||||
project_name="test",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
)
|
||||
assert config.language_backend is None
|
||||
|
||||
def test_language_backend_can_be_set(self):
|
||||
config = ProjectConfig(
|
||||
project_name="test",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
language_backend=LanguageBackend.JETBRAINS,
|
||||
)
|
||||
assert config.language_backend == LanguageBackend.JETBRAINS
|
||||
@@ -183,7 +183,7 @@ class TestProjectConfigLanguageBackend:
|
||||
def test_language_backend_roundtrips_through_yaml(self):
|
||||
config = ProjectConfig(
|
||||
project_name="test",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
language_backend=LanguageBackend.JETBRAINS,
|
||||
)
|
||||
d = config._to_yaml_dict()
|
||||
@@ -192,7 +192,7 @@ class TestProjectConfigLanguageBackend:
|
||||
def test_language_backend_none_roundtrips_through_yaml(self):
|
||||
config = ProjectConfig(
|
||||
project_name="test",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
)
|
||||
d = config._to_yaml_dict()
|
||||
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_config=ProjectConfig(
|
||||
project_name=project_name,
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
language_backend=language_backend,
|
||||
),
|
||||
serena_config=config,
|
||||
@@ -286,7 +286,7 @@ class TestEffectiveLanguageBackend:
|
||||
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "java" / "test_repo"),
|
||||
project_config=ProjectConfig(
|
||||
project_name="jb_proj",
|
||||
languages=[Language.JAVA],
|
||||
languages=[LanguageServerId.JAVA],
|
||||
language_backend=LanguageBackend.JETBRAINS,
|
||||
),
|
||||
serena_config=config,
|
||||
@@ -309,7 +309,7 @@ class TestEffectiveLanguageBackend:
|
||||
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
|
||||
project_config=ProjectConfig(
|
||||
project_name="lsp_proj2",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
language_backend=LanguageBackend.LSP,
|
||||
),
|
||||
serena_config=config,
|
||||
@@ -332,7 +332,7 @@ class TestEffectiveLanguageBackend:
|
||||
project_root=str(Path(__file__).parent.parent / "resources" / "repos" / "python" / "test_repo"),
|
||||
project_config=ProjectConfig(
|
||||
project_name="proj2",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
language_backend=None,
|
||||
),
|
||||
serena_config=config,
|
||||
@@ -432,7 +432,7 @@ class TestProjectSerenaDataFolder:
|
||||
def _make_project(self, serena_config: "SerenaConfig | None" = None) -> Project:
|
||||
project_config = ProjectConfig(
|
||||
project_name="myproject",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
)
|
||||
project = Project(
|
||||
project_root=str(self.project_path),
|
||||
@@ -679,11 +679,11 @@ class TestProjectConfigActivationCommand:
|
||||
return data
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def test_activation_command_parsed_from_dict(self):
|
||||
|
||||
@@ -8,7 +8,7 @@ import pytest
|
||||
|
||||
from serena.config.serena_config import ProjectConfig, SerenaConfig
|
||||
from serena.project import Project
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
|
||||
def _make_project(
|
||||
@@ -25,7 +25,7 @@ def _make_project(
|
||||
)
|
||||
project_config = ProjectConfig(
|
||||
project_name="test-activation",
|
||||
languages=[Language.PYTHON],
|
||||
languages=[LanguageServerId.PYTHON],
|
||||
activation_command=activation_command,
|
||||
activation_command_timeout=activation_command_timeout,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ from collections.abc import Callable
|
||||
from types import SimpleNamespace
|
||||
|
||||
from serena.dashboard import SerenaDashboardAPI
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
|
||||
class _DummyMemoryLogHandler:
|
||||
@@ -28,7 +28,7 @@ class _DummyAgent:
|
||||
return self._project
|
||||
|
||||
|
||||
def _make_dashboard(project_languages: list[Language] | None) -> SerenaDashboardAPI:
|
||||
def _make_dashboard(project_languages: list[LanguageServerId] | None) -> SerenaDashboardAPI:
|
||||
project = None
|
||||
if project_languages is not None:
|
||||
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():
|
||||
dashboard = _make_dashboard(project_languages=None)
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
available = set(response.languages)
|
||||
assert Language.PYTHON.value not in available
|
||||
assert Language.MARKDOWN.value not in available
|
||||
assert LanguageServerId.PYTHON.value not in available
|
||||
assert LanguageServerId.MARKDOWN.value not in available
|
||||
# ensure experimental languages remain available for selection
|
||||
assert Language.ANSIBLE.value in available
|
||||
assert LanguageServerId.ANSIBLE.value in available
|
||||
@@ -14,7 +14,7 @@ import pytest
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.project import Project
|
||||
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
|
||||
|
||||
pytestmark = pytest.mark.python
|
||||
@@ -73,10 +73,10 @@ class FileSystemSyncTestCase:
|
||||
def run(self, tmp_path):
|
||||
# Work on an isolated copy so we can freely create/edit/delete files under the project root.
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
# Warm the reference index, then establish the freshness baseline (first poll never notifies).
|
||||
|
||||
+126
-121
@@ -32,24 +32,24 @@ from serena.tools import (
|
||||
SafeDeleteSymbol,
|
||||
Tool,
|
||||
)
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.conftest import (
|
||||
find_identifier_pos,
|
||||
get_pytest_markers,
|
||||
get_repo_path,
|
||||
language_tests_enabled,
|
||||
language_server_tests_enabled,
|
||||
)
|
||||
from test.serena.config.test_context_mode import GROK_EXCLUDED_TOOLS
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseCase:
|
||||
language: Language
|
||||
ls_id: LanguageServerId
|
||||
id: str
|
||||
|
||||
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
|
||||
@@ -139,7 +139,6 @@ class SafeDeleteCase(BaseCase):
|
||||
|
||||
@dataclass
|
||||
class DiagnosticCase(BaseCase):
|
||||
language: Language
|
||||
relative_path: str
|
||||
name_path1: str
|
||||
name_path2: str | None
|
||||
@@ -189,8 +188,8 @@ class DiagnosticCase(BaseCase):
|
||||
|
||||
DIAGNOSTIC_CASES = [
|
||||
DiagnosticCase(
|
||||
language=Language.PYTHON,
|
||||
id=f"{Language.PYTHON.value}_missing_user",
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id=f"{LanguageServerId.PYTHON.value}_missing_user",
|
||||
relative_path=os.path.join("test_repo", "diagnostics_sample.py"),
|
||||
name_path1="broken_factory",
|
||||
name_path2="broken_consumer",
|
||||
@@ -198,8 +197,8 @@ DIAGNOSTIC_CASES = [
|
||||
message_fragment2="undefined_name",
|
||||
).to_pytest_param(),
|
||||
DiagnosticCase(
|
||||
language=Language.CLOJURE,
|
||||
id=f"{Language.CLOJURE.value}_missing-greeting",
|
||||
ls_id=LanguageServerId.CLOJURE,
|
||||
id=f"{LanguageServerId.CLOJURE.value}_missing-greeting",
|
||||
relative_path=os.path.join("src", "test_app", "diagnostics_sample.clj"),
|
||||
name_path1="broken-factory",
|
||||
name_path2="broken-consumer",
|
||||
@@ -207,8 +206,8 @@ DIAGNOSTIC_CASES = [
|
||||
message_fragment2="missing-consumer-value",
|
||||
).to_pytest_param(),
|
||||
DiagnosticCase(
|
||||
language=Language.GO,
|
||||
id=f"{Language.GO.value}_missingGreeting",
|
||||
ls_id=LanguageServerId.GO,
|
||||
id=f"{LanguageServerId.GO.value}_missingGreeting",
|
||||
relative_path="diagnostics_sample.go",
|
||||
name_path1="brokenFactory",
|
||||
name_path2="brokenConsumer",
|
||||
@@ -216,8 +215,8 @@ DIAGNOSTIC_CASES = [
|
||||
message_fragment2="missingConsumerValue",
|
||||
).to_pytest_param(),
|
||||
DiagnosticCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
id=f"{Language.TYPESCRIPT.value}_missingGreeting",
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id=f"{LanguageServerId.TYPESCRIPT.value}_missingGreeting",
|
||||
relative_path="diagnostics_sample.ts",
|
||||
name_path1="brokenFactory",
|
||||
name_path2="brokenConsumer",
|
||||
@@ -229,7 +228,7 @@ DIAGNOSTIC_CASES = [
|
||||
|
||||
FIND_DEFINING_SYMBOL_CASES = [
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_user_in_services",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
identifier="User",
|
||||
@@ -239,7 +238,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="models.py",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.PYTHON_TY,
|
||||
ls_id=LanguageServerId.PYTHON_TY,
|
||||
id="python_ty_user_in_services",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
identifier="User",
|
||||
@@ -249,7 +248,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="models.py",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.GO,
|
||||
ls_id=LanguageServerId.GO,
|
||||
id="go_helper_in_main",
|
||||
relative_path="main.go",
|
||||
identifier="Helper",
|
||||
@@ -259,7 +258,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="main.go",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.JAVA,
|
||||
ls_id=LanguageServerId.JAVA,
|
||||
id="java_model_in_main",
|
||||
relative_path=os.path.join("src", "main", "java", "test_repo", "Main.java"),
|
||||
identifier="Model",
|
||||
@@ -269,7 +268,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="Model.java",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.KOTLIN,
|
||||
ls_id=LanguageServerId.KOTLIN,
|
||||
id="kotlin_model_in_main",
|
||||
relative_path=os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
|
||||
identifier="Model",
|
||||
@@ -279,7 +278,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="Model.kt",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.RUST,
|
||||
ls_id=LanguageServerId.RUST,
|
||||
id="rust_format_greeting",
|
||||
relative_path=os.path.join("src", "main.rs"),
|
||||
identifier="format_greeting",
|
||||
@@ -289,7 +288,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="lib.rs",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.PHP,
|
||||
ls_id=LanguageServerId.PHP,
|
||||
id="php_helper_function",
|
||||
relative_path="index.php",
|
||||
identifier="helperFunction",
|
||||
@@ -299,7 +298,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="helper.php",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.CLOJURE,
|
||||
ls_id=LanguageServerId.CLOJURE,
|
||||
id="clojure_multiply_in_utils",
|
||||
relative_path=os.path.join("src", "test_app", "utils.clj"),
|
||||
identifier="multiply",
|
||||
@@ -309,7 +308,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file=os.path.join("src", "test_app", "core.clj"),
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.CSHARP,
|
||||
ls_id=LanguageServerId.CSHARP,
|
||||
id="csharp_add_in_program",
|
||||
relative_path="Program.cs",
|
||||
identifier="Add",
|
||||
@@ -319,7 +318,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="Program.cs",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.POWERSHELL,
|
||||
ls_id=LanguageServerId.POWERSHELL,
|
||||
id="powershell_convert_to_uppercase",
|
||||
relative_path="main.ps1",
|
||||
identifier="Convert-ToUpperCase",
|
||||
@@ -329,7 +328,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="utils.ps1",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.CPP,
|
||||
ls_id=LanguageServerId.CPP,
|
||||
id="cpp_add_in_a",
|
||||
relative_path="a.cpp",
|
||||
identifier="add",
|
||||
@@ -339,7 +338,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="b.cpp",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.LEAN4,
|
||||
ls_id=LanguageServerId.LEAN4,
|
||||
id="lean_add_in_main",
|
||||
relative_path="Main.lean",
|
||||
identifier="add",
|
||||
@@ -349,7 +348,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="Helper.lean",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id="typescript_helper_function",
|
||||
relative_path="index.ts",
|
||||
identifier="helperFunction",
|
||||
@@ -359,7 +358,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
expected_definition_file="index.ts",
|
||||
).to_pytest_param(),
|
||||
FindDefiningSymbolCase(
|
||||
language=Language.FSHARP,
|
||||
ls_id=LanguageServerId.FSHARP,
|
||||
id="fsharp_add_in_program",
|
||||
relative_path="Program.fs",
|
||||
identifier="add",
|
||||
@@ -374,7 +373,7 @@ FIND_DEFINING_SYMBOL_CASES = [
|
||||
|
||||
FIND_DEFINING_SYMBOL_REGEX_CASES = [
|
||||
RegexDefiningSymbolCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_import_user",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
regex=r"from \.models import Item, (User)",
|
||||
@@ -383,7 +382,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
|
||||
expected_definition_file="models.py",
|
||||
).to_pytest_param(),
|
||||
RegexDefiningSymbolCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_create_user_call",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
regex=r"=\s+(User)\(",
|
||||
@@ -392,7 +391,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
|
||||
expected_definition_file="models.py",
|
||||
).to_pytest_param(),
|
||||
RegexDefiningSymbolCase(
|
||||
language=Language.PYTHON_TY,
|
||||
ls_id=LanguageServerId.PYTHON_TY,
|
||||
id="python_ty_create_user_call",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
regex=r"=\s+(User)\(",
|
||||
@@ -401,7 +400,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
|
||||
expected_definition_file="models.py",
|
||||
).to_pytest_param(),
|
||||
RegexDefiningSymbolCase(
|
||||
language=Language.GO,
|
||||
ls_id=LanguageServerId.GO,
|
||||
id="go_greeter_var",
|
||||
relative_path="main.go",
|
||||
regex=r"var greeter (Greeter) =",
|
||||
@@ -413,7 +412,7 @@ FIND_DEFINING_SYMBOL_REGEX_CASES = [
|
||||
|
||||
FIND_IMPLEMENTATION_CASES = [
|
||||
FindImplementationCase(
|
||||
language=Language.CSHARP,
|
||||
ls_id=LanguageServerId.CSHARP,
|
||||
id="csharp_greeter_format",
|
||||
symbol_name="IGreeter/FormatGreeting",
|
||||
definition_file=os.path.join("Services", "IGreeter.cs"),
|
||||
@@ -421,7 +420,7 @@ FIND_IMPLEMENTATION_CASES = [
|
||||
expected_symbol_name="FormatGreeting",
|
||||
).to_pytest_param(),
|
||||
FindImplementationCase(
|
||||
language=Language.GO,
|
||||
ls_id=LanguageServerId.GO,
|
||||
id="go_greeter_format",
|
||||
symbol_name="Greeter/FormatGreeting",
|
||||
definition_file="main.go",
|
||||
@@ -429,7 +428,7 @@ FIND_IMPLEMENTATION_CASES = [
|
||||
expected_symbol_name="FormatGreeting",
|
||||
).to_pytest_param(),
|
||||
FindImplementationCase(
|
||||
language=Language.JAVA,
|
||||
ls_id=LanguageServerId.JAVA,
|
||||
id="java_greeter_format",
|
||||
symbol_name="Greeter/formatGreeting",
|
||||
definition_file=os.path.join("src", "main", "java", "test_repo", "Greeter.java"),
|
||||
@@ -437,7 +436,7 @@ FIND_IMPLEMENTATION_CASES = [
|
||||
expected_symbol_name="formatGreeting",
|
||||
).to_pytest_param(),
|
||||
FindImplementationCase(
|
||||
language=Language.RUST,
|
||||
ls_id=LanguageServerId.RUST,
|
||||
id="rust_greeter_format",
|
||||
symbol_name="Greeter/format_greeting",
|
||||
definition_file=os.path.join("src", "lib.rs"),
|
||||
@@ -445,7 +444,7 @@ FIND_IMPLEMENTATION_CASES = [
|
||||
expected_symbol_name="format_greeting",
|
||||
).to_pytest_param(),
|
||||
FindImplementationCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id="typescript_greeter_format",
|
||||
symbol_name="Greeter/formatGreeting",
|
||||
definition_file="formatters.ts",
|
||||
@@ -457,153 +456,157 @@ FIND_IMPLEMENTATION_CASES = [
|
||||
|
||||
FIND_SYMBOL_REFERENCES_CASES = [
|
||||
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(),
|
||||
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(),
|
||||
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(),
|
||||
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(),
|
||||
FindSymbolCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id="typescript_demo_class",
|
||||
symbol_name="DemoClass",
|
||||
expected_kind="Class",
|
||||
expected_file="index.ts",
|
||||
).to_pytest_param(),
|
||||
FindSymbolCase(
|
||||
language=Language.PHP,
|
||||
ls_id=LanguageServerId.PHP,
|
||||
id="php_helper_function",
|
||||
symbol_name="helperFunction",
|
||||
expected_kind="Function",
|
||||
expected_file="helper.php",
|
||||
).to_pytest_param(),
|
||||
FindSymbolCase(
|
||||
language=Language.CLOJURE,
|
||||
ls_id=LanguageServerId.CLOJURE,
|
||||
id="clojure_greet_function",
|
||||
symbol_name="greet",
|
||||
expected_kind="Function",
|
||||
expected_file=os.path.join("src", "test_app", "core.clj"),
|
||||
).to_pytest_param(),
|
||||
FindSymbolCase(
|
||||
language=Language.CSHARP,
|
||||
ls_id=LanguageServerId.CSHARP,
|
||||
id="csharp_calculator_class",
|
||||
symbol_name="Calculator",
|
||||
expected_kind="Class",
|
||||
expected_file="Program.cs",
|
||||
).to_pytest_param(),
|
||||
FindSymbolCase(
|
||||
language=Language.POWERSHELL,
|
||||
ls_id=LanguageServerId.POWERSHELL,
|
||||
id="powershell_greet_user",
|
||||
symbol_name="Greet-User",
|
||||
expected_kind="Function",
|
||||
expected_file="main.ps1",
|
||||
).to_pytest_param(),
|
||||
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(),
|
||||
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(),
|
||||
FindSymbolCase(
|
||||
language=Language.FSHARP,
|
||||
ls_id=LanguageServerId.FSHARP,
|
||||
id="fsharp_calculator_module",
|
||||
symbol_name="Calculator",
|
||||
expected_kind="Module",
|
||||
expected_file="Calculator.fs",
|
||||
).to_pytest_param(pytest.mark.xfail(reason="F# language server is unreliable")),
|
||||
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(),
|
||||
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(),
|
||||
]
|
||||
|
||||
FIND_REFERENCE_CASES = [
|
||||
FindReferenceCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_user_refs",
|
||||
symbol_name="User",
|
||||
definition_file=os.path.join("test_repo", "models.py"),
|
||||
reference_file=os.path.join("test_repo", "services.py"),
|
||||
).to_pytest_param(),
|
||||
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(),
|
||||
FindReferenceCase(
|
||||
language=Language.JAVA,
|
||||
ls_id=LanguageServerId.JAVA,
|
||||
id="java_model_refs",
|
||||
symbol_name="Model",
|
||||
definition_file=os.path.join("src", "main", "java", "test_repo", "Model.java"),
|
||||
reference_file=os.path.join("src", "main", "java", "test_repo", "Main.java"),
|
||||
).to_pytest_param(),
|
||||
FindReferenceCase(
|
||||
language=Language.KOTLIN,
|
||||
ls_id=LanguageServerId.KOTLIN,
|
||||
id="kotlin_model_refs",
|
||||
symbol_name="Model",
|
||||
definition_file=os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
|
||||
reference_file=os.path.join("src", "main", "kotlin", "test_repo", "Main.kt"),
|
||||
).to_pytest_param(),
|
||||
FindReferenceCase(
|
||||
language=Language.RUST,
|
||||
ls_id=LanguageServerId.RUST,
|
||||
id="rust_add_refs",
|
||||
symbol_name="add",
|
||||
definition_file=os.path.join("src", "lib.rs"),
|
||||
reference_file=os.path.join("src", "main.rs"),
|
||||
).to_pytest_param(),
|
||||
FindReferenceCase(
|
||||
language=Language.PHP,
|
||||
ls_id=LanguageServerId.PHP,
|
||||
id="php_helper_refs",
|
||||
symbol_name="helperFunction",
|
||||
definition_file="helper.php",
|
||||
reference_file="index.php",
|
||||
).to_pytest_param(),
|
||||
FindReferenceCase(
|
||||
language=Language.CLOJURE,
|
||||
ls_id=LanguageServerId.CLOJURE,
|
||||
id="clojure_multiply_refs",
|
||||
symbol_name="multiply",
|
||||
definition_file=os.path.join("src", "test_app", "core.clj"),
|
||||
reference_file=os.path.join("src", "test_app", "utils.clj"),
|
||||
).to_pytest_param(),
|
||||
FindReferenceCase(
|
||||
language=Language.CSHARP,
|
||||
ls_id=LanguageServerId.CSHARP,
|
||||
id="csharp_calculator_refs",
|
||||
symbol_name="Calculator",
|
||||
definition_file="Program.cs",
|
||||
reference_file="Program.cs",
|
||||
).to_pytest_param(),
|
||||
FindReferenceCase(
|
||||
language=Language.POWERSHELL,
|
||||
ls_id=LanguageServerId.POWERSHELL,
|
||||
id="powershell_greet_user_refs",
|
||||
symbol_name="Greet-User",
|
||||
definition_file="main.ps1",
|
||||
reference_file="main.ps1",
|
||||
).to_pytest_param(),
|
||||
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(),
|
||||
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(),
|
||||
FindReferenceCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id="typescript_helper_refs",
|
||||
symbol_name="helperFunction",
|
||||
definition_file="index.ts",
|
||||
reference_file="use_helper.ts",
|
||||
).to_pytest_param(pytest.mark.xfail(False, reason="TypeScript language server is unreliable")),
|
||||
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(
|
||||
pytest.mark.xfail(reason="F# language server is unreliable"), # See issue #1040
|
||||
),
|
||||
FindReferenceCase(
|
||||
language=Language.LATEX,
|
||||
ls_id=LanguageServerId.LATEX,
|
||||
id="latex_background_refs",
|
||||
symbol_name="Background",
|
||||
definition_file="sections/background.tex",
|
||||
@@ -613,7 +616,7 @@ FIND_REFERENCE_CASES = [
|
||||
|
||||
FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [
|
||||
RegexDefiningSymbolErrorCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_regex_multiple_matches",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
regex=r"(User)",
|
||||
@@ -621,7 +624,7 @@ FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [
|
||||
error_fragment="Match must be unique",
|
||||
).to_pytest_param(),
|
||||
RegexDefiningSymbolErrorCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_regex_missing_group",
|
||||
relative_path=os.path.join("test_repo", "services.py"),
|
||||
regex=r"self.users.get\(id\)",
|
||||
@@ -632,7 +635,7 @@ FIND_DEFINING_SYMBOL_REGEX_ERROR_CASES = [
|
||||
|
||||
FIND_SYMBOL_NAME_PATH_CASES = [
|
||||
FindSymbolNamePathCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="nested_class_exact",
|
||||
name_path="OuterClass/NestedClass",
|
||||
substring_matching=False,
|
||||
@@ -641,7 +644,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
|
||||
expected_file=os.path.join("test_repo", "nested.py"),
|
||||
).to_pytest_param(),
|
||||
FindSymbolNamePathCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="nested_method_exact",
|
||||
name_path="OuterClass/NestedClass/find_me",
|
||||
substring_matching=False,
|
||||
@@ -650,7 +653,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
|
||||
expected_file=os.path.join("test_repo", "nested.py"),
|
||||
).to_pytest_param(),
|
||||
FindSymbolNamePathCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="nested_class_substring",
|
||||
name_path="OuterClass/NestedCl",
|
||||
substring_matching=True,
|
||||
@@ -659,7 +662,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
|
||||
expected_file=os.path.join("test_repo", "nested.py"),
|
||||
).to_pytest_param(),
|
||||
FindSymbolNamePathCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="nested_method_substring",
|
||||
name_path="OuterClass/NestedClass/find_m",
|
||||
substring_matching=True,
|
||||
@@ -668,7 +671,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
|
||||
expected_file=os.path.join("test_repo", "nested.py"),
|
||||
).to_pytest_param(),
|
||||
FindSymbolNamePathCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="outer_class_absolute",
|
||||
name_path="/OuterClass",
|
||||
substring_matching=False,
|
||||
@@ -677,7 +680,7 @@ FIND_SYMBOL_NAME_PATH_CASES = [
|
||||
expected_file=os.path.join("test_repo", "nested.py"),
|
||||
).to_pytest_param(),
|
||||
FindSymbolNamePathCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="nested_method_absolute_substring",
|
||||
name_path="/OuterClass/NestedClass/find_m",
|
||||
substring_matching=True,
|
||||
@@ -688,21 +691,21 @@ FIND_SYMBOL_NAME_PATH_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(
|
||||
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(),
|
||||
]
|
||||
|
||||
FIND_SYMBOL_OVERLOADED_FUNCTION_CASES = [
|
||||
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(),
|
||||
]
|
||||
|
||||
NON_UNIQUE_SYMBOL_REFERENCE_ERROR_CASES = [
|
||||
NonUniqueSymbolReferenceCase(
|
||||
language=Language.JAVA,
|
||||
ls_id=LanguageServerId.JAVA,
|
||||
id="java_overloaded_get_name",
|
||||
name_path="Model/getName",
|
||||
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 = [
|
||||
SafeDeleteCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_user",
|
||||
name_path="User",
|
||||
relative_path=os.path.join("test_repo", "models.py"),
|
||||
).to_pytest_param(),
|
||||
SafeDeleteCase(
|
||||
language=Language.JAVA,
|
||||
ls_id=LanguageServerId.JAVA,
|
||||
id="java_model",
|
||||
name_path="Model",
|
||||
relative_path=os.path.join("src", "main", "java", "test_repo", "Model.java"),
|
||||
).to_pytest_param(),
|
||||
SafeDeleteCase(
|
||||
language=Language.KOTLIN,
|
||||
ls_id=LanguageServerId.KOTLIN,
|
||||
id="kotlin_model",
|
||||
name_path="Model",
|
||||
relative_path=os.path.join("src", "main", "kotlin", "test_repo", "Model.kt"),
|
||||
).to_pytest_param(),
|
||||
SafeDeleteCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id="typescript_helper_function",
|
||||
name_path="helperFunction",
|
||||
relative_path="index.ts",
|
||||
@@ -738,25 +741,25 @@ SAFE_DELETE_BLOCKED_CASES = [
|
||||
|
||||
SAFE_DELETE_SUCCEEDS_CASES = [
|
||||
SafeDeleteCase(
|
||||
language=Language.PYTHON,
|
||||
ls_id=LanguageServerId.PYTHON,
|
||||
id="python_timer",
|
||||
name_path="Timer",
|
||||
relative_path=os.path.join("test_repo", "utils.py"),
|
||||
).to_pytest_param(),
|
||||
SafeDeleteCase(
|
||||
language=Language.JAVA,
|
||||
ls_id=LanguageServerId.JAVA,
|
||||
id="java_model_user",
|
||||
name_path="ModelUser",
|
||||
relative_path=os.path.join("src", "main", "java", "test_repo", "ModelUser.java"),
|
||||
).to_pytest_param(),
|
||||
SafeDeleteCase(
|
||||
language=Language.KOTLIN,
|
||||
ls_id=LanguageServerId.KOTLIN,
|
||||
id="kotlin_model_user",
|
||||
name_path="ModelUser",
|
||||
relative_path=os.path.join("src", "main", "kotlin", "test_repo", "ModelUser.kt"),
|
||||
).to_pytest_param(),
|
||||
SafeDeleteCase(
|
||||
language=Language.TYPESCRIPT,
|
||||
ls_id=LanguageServerId.TYPESCRIPT,
|
||||
id="typescript_unused_standalone_function",
|
||||
name_path="unusedStandaloneFunction",
|
||||
relative_path="index.ts",
|
||||
@@ -771,23 +774,23 @@ def serena_config():
|
||||
# Create test projects for all supported languages
|
||||
test_projects = []
|
||||
for language in [
|
||||
Language.PYTHON,
|
||||
Language.PYTHON_TY,
|
||||
Language.GO,
|
||||
Language.JAVA,
|
||||
Language.KOTLIN,
|
||||
Language.RUST,
|
||||
Language.TYPESCRIPT,
|
||||
Language.PHP,
|
||||
Language.CSHARP,
|
||||
Language.CLOJURE,
|
||||
Language.FSHARP,
|
||||
Language.POWERSHELL,
|
||||
Language.CPP,
|
||||
Language.HAXE,
|
||||
Language.LEAN4,
|
||||
Language.MSL,
|
||||
Language.LATEX,
|
||||
LanguageServerId.PYTHON,
|
||||
LanguageServerId.PYTHON_TY,
|
||||
LanguageServerId.GO,
|
||||
LanguageServerId.JAVA,
|
||||
LanguageServerId.KOTLIN,
|
||||
LanguageServerId.RUST,
|
||||
LanguageServerId.TYPESCRIPT,
|
||||
LanguageServerId.PHP,
|
||||
LanguageServerId.CSHARP,
|
||||
LanguageServerId.CLOJURE,
|
||||
LanguageServerId.FSHARP,
|
||||
LanguageServerId.POWERSHELL,
|
||||
LanguageServerId.CPP,
|
||||
LanguageServerId.HAXE,
|
||||
LanguageServerId.LEAN4,
|
||||
LanguageServerId.MSL,
|
||||
LanguageServerId.LATEX,
|
||||
]:
|
||||
repo_path = get_repo_path(language)
|
||||
if repo_path.exists():
|
||||
@@ -845,8 +848,8 @@ def project_file_modification_context(serena_agent: SerenaAgent, relative_path:
|
||||
|
||||
@pytest.fixture
|
||||
def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[SerenaAgent]:
|
||||
language = Language(request.param)
|
||||
if not language_tests_enabled(language):
|
||||
language = LanguageServerId(request.param)
|
||||
if not language_server_tests_enabled(language):
|
||||
pytest.skip(f"Tests for language {language} are not enabled.")
|
||||
|
||||
project_name = f"test_repo_{language}"
|
||||
@@ -865,7 +868,7 @@ def serena_agent(request: pytest.FixtureRequest, serena_config) -> Iterator[Sere
|
||||
class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"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"],
|
||||
)
|
||||
def test_agent_instantiation(self, project: str | None):
|
||||
@@ -880,7 +883,7 @@ class TestSerenaAgent:
|
||||
SerenaAgent(project=project, serena_config=serena_config)
|
||||
|
||||
@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):
|
||||
agent = SerenaAgent(
|
||||
project="test_repo_python",
|
||||
@@ -911,7 +914,7 @@ class TestSerenaAgent:
|
||||
symbol: dict,
|
||||
expected_name: str | 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
|
||||
return
|
||||
|
||||
@@ -924,12 +927,12 @@ class TestSerenaAgent:
|
||||
|
||||
if expected_name is not None:
|
||||
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}"
|
||||
)
|
||||
|
||||
# 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}"
|
||||
|
||||
@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)
|
||||
|
||||
# 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)
|
||||
pos2 = find_identifier_pos(project_root / diagnostic_case.relative_path, cast(str, diagnostic_case.symbol2_id_str))
|
||||
assert pos1 is not None
|
||||
@@ -1141,7 +1144,7 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"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"],
|
||||
)
|
||||
@@ -1163,7 +1166,7 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"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"],
|
||||
)
|
||||
@@ -1191,7 +1194,7 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"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"],
|
||||
)
|
||||
@@ -1221,7 +1224,7 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"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"],
|
||||
)
|
||||
@@ -1242,8 +1245,8 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"serena_agent",
|
||||
[
|
||||
pytest.param(Language.PYTHON, marks=get_pytest_markers(Language.PYTHON), id="python_services"),
|
||||
pytest.param(Language.PYTHON_TY, marks=get_pytest_markers(Language.PYTHON_TY), id="python_ty_services"),
|
||||
pytest.param(LanguageServerId.PYTHON, marks=get_pytest_markers(LanguageServerId.PYTHON), id="python_services"),
|
||||
pytest.param(LanguageServerId.PYTHON_TY, marks=get_pytest_markers(LanguageServerId.PYTHON_TY), id="python_ty_services"),
|
||||
],
|
||||
indirect=["serena_agent"],
|
||||
)
|
||||
@@ -1273,8 +1276,8 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"serena_agent",
|
||||
[
|
||||
pytest.param(Language.PYTHON, marks=get_pytest_markers(Language.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, marks=get_pytest_markers(LanguageServerId.PYTHON), id="python_container_body"),
|
||||
pytest.param(LanguageServerId.PYTHON_TY, marks=get_pytest_markers(LanguageServerId.PYTHON_TY), id="python_ty_container_body"),
|
||||
],
|
||||
indirect=["serena_agent"],
|
||||
)
|
||||
@@ -1306,7 +1309,9 @@ class TestSerenaAgent:
|
||||
@pytest.mark.parametrize(
|
||||
"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"],
|
||||
)
|
||||
@@ -1375,7 +1380,7 @@ class TestPromptProvision:
|
||||
else:
|
||||
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:
|
||||
"""
|
||||
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)
|
||||
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:
|
||||
"""
|
||||
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
|
||||
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:
|
||||
project_name = "test_repo_python"
|
||||
session = "session1"
|
||||
|
||||
@@ -22,9 +22,9 @@ from overrides import overrides
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
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 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
|
||||
|
||||
@@ -177,14 +177,14 @@ class CodeDiff:
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
self.rel_path = rel_path
|
||||
self.language = language
|
||||
self.original_repo_path = get_repo_path(language)
|
||||
self.ls_id = ls_id
|
||||
self.original_repo_path = get_repo_path(ls_id)
|
||||
self.repo_path: Path | None = None
|
||||
|
||||
@contextmanager
|
||||
@@ -199,8 +199,8 @@ class EditingTest(ABC):
|
||||
# wait for a long time here
|
||||
if os.name == "nt":
|
||||
time.sleep(0.1)
|
||||
log.info(f"Creating language server for {self.language} {self.rel_path}")
|
||||
with project_with_ls_context(self.language, str(self.repo_path)) as project:
|
||||
log.info(f"Creating language server for {self.ls_id} {self.rel_path}")
|
||||
with project_with_ls_context(self.ls_id, str(self.repo_path)) as project:
|
||||
yield LanguageServerSymbolRetriever(project)
|
||||
finally:
|
||||
# prevent deadlock on Windows due to lingering file locks
|
||||
@@ -244,8 +244,8 @@ TYPESCRIPT_TEST_FILE = "index.ts"
|
||||
|
||||
|
||||
class DeleteSymbolTest(EditingTest):
|
||||
def __init__(self, language: Language, rel_path: str, deleted_symbol: str):
|
||||
super().__init__(language, rel_path)
|
||||
def __init__(self, ls_id: LanguageServerId, rel_path: str, deleted_symbol: str):
|
||||
super().__init__(ls_id, rel_path)
|
||||
self.deleted_symbol = deleted_symbol
|
||||
self.rel_path = rel_path
|
||||
|
||||
@@ -258,7 +258,7 @@ class DeleteSymbolTest(EditingTest):
|
||||
[
|
||||
pytest.param(
|
||||
DeleteSymbolTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"VariableContainer",
|
||||
),
|
||||
@@ -266,7 +266,7 @@ class DeleteSymbolTest(EditingTest):
|
||||
),
|
||||
pytest.param(
|
||||
DeleteSymbolTest(
|
||||
Language.TYPESCRIPT,
|
||||
LanguageServerId.TYPESCRIPT,
|
||||
TYPESCRIPT_TEST_FILE,
|
||||
"DemoClass",
|
||||
),
|
||||
@@ -307,9 +307,9 @@ NEW_TYPESCRIPT_FUNCTION_AFTER = """function newFunctionAfterClass(): void {
|
||||
|
||||
class InsertInRelToSymbolTest(EditingTest):
|
||||
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.new_content = new_content
|
||||
self.mode: Literal["before", "after"] | None = mode
|
||||
@@ -331,7 +331,7 @@ class InsertInRelToSymbolTest(EditingTest):
|
||||
[
|
||||
pytest.param(
|
||||
InsertInRelToSymbolTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"use_module_variables",
|
||||
NEW_PYTHON_FUNCTION,
|
||||
@@ -340,7 +340,7 @@ class InsertInRelToSymbolTest(EditingTest):
|
||||
),
|
||||
pytest.param(
|
||||
InsertInRelToSymbolTest(
|
||||
Language.TYPESCRIPT,
|
||||
LanguageServerId.TYPESCRIPT,
|
||||
TYPESCRIPT_TEST_FILE,
|
||||
"DemoClass",
|
||||
NEW_TYPESCRIPT_FUNCTION_AFTER,
|
||||
@@ -349,7 +349,7 @@ class InsertInRelToSymbolTest(EditingTest):
|
||||
),
|
||||
pytest.param(
|
||||
InsertInRelToSymbolTest(
|
||||
Language.TYPESCRIPT,
|
||||
LanguageServerId.TYPESCRIPT,
|
||||
TYPESCRIPT_TEST_FILE,
|
||||
"helperFunction",
|
||||
NEW_TYPESCRIPT_FUNCTION,
|
||||
@@ -366,7 +366,7 @@ def test_insert_in_rel_to_symbol(test_case: InsertInRelToSymbolTest, mode: Liter
|
||||
@pytest.mark.python
|
||||
def test_insert_python_class_before(snapshot: SnapshotAssertion):
|
||||
InsertInRelToSymbolTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"VariableDataclass",
|
||||
NEW_PYTHON_CLASS_WITH_TRAILING_NEWLINES,
|
||||
@@ -377,7 +377,7 @@ def test_insert_python_class_before(snapshot: SnapshotAssertion):
|
||||
@pytest.mark.python
|
||||
def test_insert_python_class_after(snapshot: SnapshotAssertion):
|
||||
InsertInRelToSymbolTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"VariableDataclass",
|
||||
NEW_PYTHON_CLASS_WITH_LEADING_NEWLINES,
|
||||
@@ -399,8 +399,8 @@ TYPESCRIPT_REPLACED_BODY = """function printValue() {
|
||||
|
||||
|
||||
class ReplaceBodyTest(EditingTest):
|
||||
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_body: str):
|
||||
super().__init__(language, rel_path)
|
||||
def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_body: str):
|
||||
super().__init__(ls_id, rel_path)
|
||||
self.symbol_name = symbol_name
|
||||
self.new_body = new_body
|
||||
|
||||
@@ -413,7 +413,7 @@ class ReplaceBodyTest(EditingTest):
|
||||
[
|
||||
pytest.param(
|
||||
ReplaceBodyTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"VariableContainer/modify_instance_var",
|
||||
PYTHON_REPLACED_BODY,
|
||||
@@ -422,7 +422,7 @@ class ReplaceBodyTest(EditingTest):
|
||||
),
|
||||
pytest.param(
|
||||
ReplaceBodyTest(
|
||||
Language.TYPESCRIPT,
|
||||
LanguageServerId.TYPESCRIPT,
|
||||
TYPESCRIPT_TEST_FILE,
|
||||
"DemoClass/printValue",
|
||||
TYPESCRIPT_REPLACED_BODY,
|
||||
@@ -442,8 +442,8 @@ NIX_ATTR_REPLACEMENT = """c = 3;"""
|
||||
class NixAttrReplacementTest(EditingTest):
|
||||
"""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):
|
||||
super().__init__(language, rel_path)
|
||||
def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_body: str):
|
||||
super().__init__(ls_id, rel_path)
|
||||
self.symbol_name = symbol_name
|
||||
self.new_body = new_body
|
||||
|
||||
@@ -452,7 +452,7 @@ class NixAttrReplacementTest(EditingTest):
|
||||
|
||||
|
||||
@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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
test_case = NixAttrReplacementTest(
|
||||
Language.NIX,
|
||||
LanguageServerId.NIX,
|
||||
"default.nix",
|
||||
"testUser", # Simple attrset with multiple key-value pairs
|
||||
NIX_ATTR_REPLACEMENT,
|
||||
@@ -486,8 +486,8 @@ class GoDeclReplacementTest(EditingTest):
|
||||
in a duplicated leading keyword (e.g. ``type type NamedInt``).
|
||||
"""
|
||||
|
||||
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_body: str):
|
||||
super().__init__(language, rel_path)
|
||||
def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_body: str):
|
||||
super().__init__(ls_id, rel_path)
|
||||
self.symbol_name = symbol_name
|
||||
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.
|
||||
"""
|
||||
test_case = GoDeclReplacementTest(
|
||||
Language.GO,
|
||||
LanguageServerId.GO,
|
||||
"symbol_body.go",
|
||||
"NamedInt",
|
||||
GO_DECL_REPLACEMENT,
|
||||
@@ -532,8 +532,8 @@ def test_go_symbol_replacement_no_double_keyword(snapshot: SnapshotAssertion):
|
||||
|
||||
|
||||
class RenameSymbolTest(EditingTest):
|
||||
def __init__(self, language: Language, rel_path: str, symbol_name: str, new_name: str):
|
||||
super().__init__(language, rel_path)
|
||||
def __init__(self, ls_id: LanguageServerId, rel_path: str, symbol_name: str, new_name: str):
|
||||
super().__init__(ls_id, rel_path)
|
||||
self.symbol_name = symbol_name
|
||||
self.new_name = new_name
|
||||
|
||||
@@ -550,7 +550,7 @@ class RenameSymbolTest(EditingTest):
|
||||
@pytest.mark.python
|
||||
def test_rename_symbol(snapshot: SnapshotAssertion):
|
||||
test_case = RenameSymbolTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"typed_module_var",
|
||||
"renamed_typed_module_var",
|
||||
@@ -564,15 +564,15 @@ class InsertAndDeleteSymbolTest(EditingTest):
|
||||
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_symbol: symbol after which the insertion is made
|
||||
:param deleted_symbol: the symbol to be deleted after the insertion
|
||||
: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.deleted_symbol = deleted_symbol
|
||||
self.rel_path = rel_path
|
||||
@@ -592,7 +592,7 @@ def test_insert_and_delete_no_change():
|
||||
"""
|
||||
insertion = " def inserted_function():\n pass"
|
||||
test_case = InsertAndDeleteSymbolTest(
|
||||
Language.PYTHON,
|
||||
LanguageServerId.PYTHON,
|
||||
PYTHON_TEST_REL_FILE_PATH,
|
||||
"VariableContainer/modify_instance_var",
|
||||
"VariableContainer/inserted_function",
|
||||
@@ -617,7 +617,7 @@ NEW_VUE_HANDLER = """const handleDoubleClick = () => {
|
||||
[
|
||||
pytest.param(
|
||||
DeleteSymbolTest(
|
||||
Language.VUE,
|
||||
LanguageServerId.VUE,
|
||||
VUE_TEST_FILE,
|
||||
"handleMouseEnter",
|
||||
),
|
||||
@@ -635,7 +635,7 @@ def test_delete_symbol_vue(test_case: DeleteSymbolTest, snapshot: SnapshotAssert
|
||||
[
|
||||
pytest.param(
|
||||
InsertInRelToSymbolTest(
|
||||
Language.VUE,
|
||||
LanguageServerId.VUE,
|
||||
VUE_TEST_FILE,
|
||||
"handleClick",
|
||||
NEW_VUE_HANDLER,
|
||||
@@ -666,7 +666,7 @@ VUE_REPLACED_HANDLECLICK_BODY = """const handleClick = () => {
|
||||
[
|
||||
pytest.param(
|
||||
ReplaceBodyTest(
|
||||
Language.VUE,
|
||||
LanguageServerId.VUE,
|
||||
VUE_TEST_FILE,
|
||||
"handleClick",
|
||||
VUE_REPLACED_HANDLECLICK_BODY,
|
||||
@@ -687,7 +687,7 @@ VUE_REPLACED_PRESSCOUNT_BODY = """const pressCount = ref(100)"""
|
||||
[
|
||||
pytest.param(
|
||||
ReplaceBodyTest(
|
||||
Language.VUE,
|
||||
LanguageServerId.VUE,
|
||||
VUE_TEST_FILE,
|
||||
"pressCount",
|
||||
VUE_REPLACED_PRESSCOUNT_BODY,
|
||||
@@ -726,7 +726,7 @@ VUE_STORE_REPLACED_CLEAR_BODY = """function clear() {
|
||||
[
|
||||
pytest.param(
|
||||
ReplaceBodyTest(
|
||||
Language.VUE,
|
||||
LanguageServerId.VUE,
|
||||
VUE_STORE_FILE,
|
||||
"clear",
|
||||
VUE_STORE_REPLACED_CLEAR_BODY,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Unit tests for :func:`serena.util.inspection.determine_programming_language_composition`."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from serena.util.inspection import determine_programming_language_composition
|
||||
from solidlsp.ls_config import Language
|
||||
from serena.util.inspection import compute_language_server_support_composition
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
|
||||
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")
|
||||
|
||||
|
||||
class TestDetermineProgrammingLanguageComposition:
|
||||
class TestComputeLanguageServerSupportComposition:
|
||||
def test_single_language_repo(self, tmp_path: Path) -> None:
|
||||
_touch(tmp_path, "a.py", "b.py", "c.py")
|
||||
composition = determine_programming_language_composition(str(tmp_path))
|
||||
assert composition[Language.PYTHON] == 100.0
|
||||
composition = compute_language_server_support_composition(str(tmp_path))
|
||||
assert composition[LanguageServerId.PYTHON] == 100.0
|
||||
|
||||
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
|
||||
@@ -23,23 +21,23 @@ class TestDetermineProgrammingLanguageComposition:
|
||||
_touch(tmp_path, *[f"note_{i}.txt" for i in range(20)])
|
||||
_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
|
||||
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:
|
||||
_touch(tmp_path, "a.py", "b.py", "c.py", "d.go")
|
||||
_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[Language.GO] == 25.0
|
||||
assert composition[LanguageServerId.PYTHON] == 75.0
|
||||
assert composition[LanguageServerId.GO] == 25.0
|
||||
|
||||
def test_repo_without_recognised_files(self, tmp_path: Path) -> None:
|
||||
_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:
|
||||
assert determine_programming_language_composition(str(tmp_path)) == {}
|
||||
assert compute_language_server_support_composition(str(tmp_path)) == {}
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ class TestAdaLanguageServer:
|
||||
obviously test-affecting.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
assert language_server.is_running()
|
||||
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("repo_path", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
# main.adb (1-indexed source / 0-indexed LSP):
|
||||
# 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).
|
||||
assert loc["range"]["start"]["line"] == 4
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
# main.adb LSP line 4 / column 40 sits on the `G` of `Helper.Greet`:
|
||||
# ` Greeting : constant String := Helper.Greet ("Ada");`
|
||||
@@ -65,8 +65,8 @@ class TestAdaLanguageServer:
|
||||
assert loc["range"]["start"]["line"] == 4
|
||||
assert loc["range"]["start"]["character"] == 12
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
# 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
|
||||
@@ -80,8 +80,8 @@ class TestAdaLanguageServer:
|
||||
# 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}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
# Click on the `G` of `Greet` in the spec:
|
||||
# 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.
|
||||
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:
|
||||
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, "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:
|
||||
doc_symbols = language_server.request_document_symbols(str(Path("src") / "helper.ads"))
|
||||
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 "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:
|
||||
"""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()
|
||||
@@ -129,7 +129,7 @@ class TestAdaLanguageServer:
|
||||
# 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}"
|
||||
|
||||
@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:
|
||||
# ALS surfaces a few Ada-specific synthetic groupings as symbols:
|
||||
# - "With clauses" — namespace-kind group containing all `with` statements in a unit
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.ada
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -5,13 +5,13 @@ import pytest
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.language_servers.al_language_server import ALLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
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
|
||||
|
||||
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:
|
||||
@@ -62,7 +62,7 @@ class TestExtractALDisplayName:
|
||||
|
||||
@pytest.mark.al
|
||||
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:
|
||||
"""Test that AL symbol names are normalized (metadata stripped)."""
|
||||
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'"
|
||||
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:
|
||||
"""Test that find_symbol can match AL symbols by normalized name without substring_matching."""
|
||||
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.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:
|
||||
"""Test finding a codeunit by its normalized name."""
|
||||
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.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:
|
||||
"""Test that AL Language Server can find symbols in the test repository with normalized names."""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
@@ -136,7 +136,7 @@ class TestALLanguageServer:
|
||||
# Check for interface symbol
|
||||
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:
|
||||
"""Test that AL Language Server can find fields within a table."""
|
||||
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("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:
|
||||
"""Test that AL Language Server can find procedures in codeunits."""
|
||||
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("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:
|
||||
"""Test that AL Language Server can find references to symbols."""
|
||||
# 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"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""Test that AL Language Server can handle cross-file symbol relationships."""
|
||||
# Get all symbols to verify cross-file visibility
|
||||
@@ -290,7 +290,7 @@ class TestALHoverInjection:
|
||||
return hover, 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:
|
||||
"""Test that hovering over a Table symbol shows the full object name with ID."""
|
||||
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 '**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:
|
||||
"""Test that hovering over a Page symbol shows the full object name with ID."""
|
||||
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 '**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:
|
||||
"""Test that hovering over a Codeunit symbol shows the full object name with ID."""
|
||||
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 "**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:
|
||||
"""Test that hovering over an Enum symbol shows the full object name with ID."""
|
||||
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 "**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:
|
||||
"""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")
|
||||
@@ -340,7 +340,7 @@ class TestALHoverInjection:
|
||||
assert value is not None, "Hover should have content"
|
||||
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:
|
||||
"""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")
|
||||
@@ -353,7 +353,7 @@ class TestALHoverInjection:
|
||||
# But should contain procedure info
|
||||
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:
|
||||
"""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")
|
||||
@@ -380,7 +380,7 @@ class TestALHoverInjection:
|
||||
|
||||
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:
|
||||
"""Test that multiple AL objects each get their correct full name injected."""
|
||||
test_cases = [
|
||||
@@ -398,7 +398,7 @@ class TestALHoverInjection:
|
||||
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:
|
||||
"""Test that injected hover has a separator between injected name and original content."""
|
||||
file_path = os.path.join("src", "Tables", "Customer.Table.al")
|
||||
@@ -413,7 +413,7 @@ class TestALHoverInjection:
|
||||
separator_pos = value.find("---")
|
||||
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:
|
||||
"""Test that the original hover content is preserved after the injected name."""
|
||||
file_path = os.path.join("src", "Tables", "Customer.Table.al")
|
||||
@@ -430,7 +430,7 @@ class TestALHoverInjection:
|
||||
class TestALPathNormalization:
|
||||
"""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:
|
||||
"""Test that hover injection works with forward slash paths."""
|
||||
file_path = "src/Tables/Customer.Table.al"
|
||||
@@ -451,7 +451,7 @@ class TestALPathNormalization:
|
||||
|
||||
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:
|
||||
"""Test that hover injection works with backslash paths (Windows style)."""
|
||||
file_path = "src\\Tables\\Customer.Table.al"
|
||||
@@ -472,7 +472,7 @@ class TestALPathNormalization:
|
||||
|
||||
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:
|
||||
"""Test hover works when symbols requested with backslash but hover with forward slash."""
|
||||
file_path_backslash = "src\\Tables\\Customer.Table.al"
|
||||
@@ -499,7 +499,7 @@ class TestALPathNormalization:
|
||||
|
||||
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:
|
||||
"""Test hover works when symbols requested with forward slash but hover with backslash."""
|
||||
file_path_forward = "src/Tables/Customer.Table.al"
|
||||
@@ -526,7 +526,7 @@ class TestALPathNormalization:
|
||||
|
||||
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:
|
||||
"""Test that hover injection cache works correctly across multiple files with different path formats."""
|
||||
test_cases = [
|
||||
@@ -559,7 +559,7 @@ class TestALPathNormalization:
|
||||
)
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -17,19 +17,19 @@ import pytest
|
||||
|
||||
from serena.util.text_utils import find_text_coordinates
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.angular
|
||||
class TestAngularLanguageServerBasics:
|
||||
@pytest.mark.parametrize("language_server", [Language.ANGULAR], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.ANGULAR], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
assert language_server.is_running()
|
||||
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:
|
||||
"""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()
|
||||
@@ -37,14 +37,14 @@ class TestAngularLanguageServerBasics:
|
||||
for expected in ("AppComponent", "title", "userName", "items", "greeting", "setName"):
|
||||
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:
|
||||
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]
|
||||
for expected in ("GreetingService", "greet", "defaultName"):
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
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}"
|
||||
|
||||
@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:
|
||||
"""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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""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"):
|
||||
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:
|
||||
"""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"):
|
||||
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:
|
||||
"""References on a .ts component method must include its template callers.
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestAngularLanguageServerBasics:
|
||||
class TestAngularHover:
|
||||
"""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:
|
||||
"""Hover on a .ts method declaration is routed through the companion TS server
|
||||
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)
|
||||
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:
|
||||
"""Hover on a method call inside an Angular template ({{ greeting() }}) goes
|
||||
through ngserver and must yield Angular-aware type info.
|
||||
@@ -174,7 +174,7 @@ class TestAngularHover:
|
||||
class TestAngularDefinitionRouting:
|
||||
"""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:
|
||||
"""Property binding ``[value]="userName()"`` must resolve to the component's
|
||||
``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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""Event binding ``(input)="setName(...)"`` must resolve to the component's
|
||||
``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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""The ``GreetingService`` symbol used in the constructor parameter list
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""The ``<app-item-card>`` element in the parent template must resolve to the
|
||||
ItemCardComponent class declaration in item-card.component.ts.
|
||||
@@ -258,7 +258,7 @@ class TestAngularDefinitionRouting:
|
||||
class TestAngularRename:
|
||||
"""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:
|
||||
"""Renaming the ``setName`` method from its .ts declaration must return a
|
||||
WorkspaceEdit that touches both app.component.ts (declaration + any TS calls)
|
||||
@@ -289,7 +289,7 @@ class TestAngularRename:
|
||||
class TestAngularSymbolStructure:
|
||||
"""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:
|
||||
"""``request_document_symbols`` should return AppComponent as a class symbol
|
||||
whose children include its methods (``greeting``, ``setName``) and signal
|
||||
@@ -306,7 +306,7 @@ class TestAngularSymbolStructure:
|
||||
for expected in ("greeting", "setName", "title", "userName", "items"):
|
||||
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:
|
||||
"""A custom pipe (``ExclaimPipe``) declared in exclaim.pipe.ts must appear in
|
||||
the workspace symbol tree alongside the components and the service.
|
||||
@@ -321,7 +321,7 @@ class TestAngularSymbolStructure:
|
||||
class TestAngularImplementations:
|
||||
"""``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:
|
||||
"""``Greeter.greet`` is implemented by ``GreetingService``; ``request_implementation``
|
||||
invoked at the interface declaration must point at the service.
|
||||
|
||||
@@ -12,13 +12,13 @@ Two paths are exercised:
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.angular
|
||||
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:
|
||||
"""The component's ``count: number = 'not-a-number'`` must be flagged by tsserver."""
|
||||
assert_file_diagnostics(
|
||||
@@ -28,7 +28,7 @@ class TestAngularDiagnostics:
|
||||
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:
|
||||
"""The template's ``{{ undefinedSignal() }}`` must be flagged by ngserver.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import time
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from test.conftest import _create_ls
|
||||
|
||||
@@ -37,7 +37,7 @@ TEMPLATE_FILE = os.path.join("src", "app", "app.component.html")
|
||||
class TestAngularInvalidPositionsOnTs:
|
||||
"""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:
|
||||
"""``request_containing_symbol`` short-circuits before reaching the LS:
|
||||
a negative line returns None.
|
||||
@@ -45,19 +45,19 @@ class TestAngularInvalidPositionsOnTs:
|
||||
result = language_server.request_containing_symbol(TS_FILE, -1, 0)
|
||||
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:
|
||||
result = language_server.request_containing_symbol(TS_FILE, 5, -1)
|
||||
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:
|
||||
"""The wrapper code raises ``IndexError`` before reaching the LS."""
|
||||
with pytest.raises(IndexError) as exc_info:
|
||||
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}"
|
||||
|
||||
@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:
|
||||
"""Character far beyond end-of-line is clamped by the LSP. Line 5 of
|
||||
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 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:
|
||||
"""The Angular LS's TS companion returns ``[]`` for negative-line
|
||||
``request_references`` — it does **not** raise. (Plain Vue tests
|
||||
@@ -79,7 +79,7 @@ class TestAngularInvalidPositionsOnTs:
|
||||
result = language_server.request_references(TS_FILE, -1, 0)
|
||||
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:
|
||||
"""Same as references: TS companion returns ``[]``, does not raise."""
|
||||
result = language_server.request_definition(TS_FILE, -1, 0)
|
||||
@@ -93,7 +93,7 @@ class TestAngularInvalidPositionsOnTs:
|
||||
class TestAngularInvalidPositionsOnTemplate:
|
||||
"""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:
|
||||
"""Ngserver does **not** swallow malformed positions: it surfaces a
|
||||
``Debug Failure. False expression.`` error from its underlying
|
||||
@@ -105,7 +105,7 @@ class TestAngularInvalidPositionsOnTemplate:
|
||||
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:
|
||||
"""Same path as negative line — ngserver raises Debug Failure."""
|
||||
with pytest.raises(SolidLSPException) as exc_info:
|
||||
@@ -118,7 +118,7 @@ class TestAngularInvalidPositionsOnTemplate:
|
||||
class TestAngularNonExistentFiles:
|
||||
"""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:
|
||||
nonexistent = os.path.join("src", "app", "does-not-exist.component.ts")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
@@ -128,7 +128,7 @@ class TestAngularNonExistentFiles:
|
||||
with pytest.raises(FileNotFoundError):
|
||||
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:
|
||||
nonexistent = os.path.join("src", "app", "does-not-exist.component.html")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
@@ -140,7 +140,7 @@ class TestAngularNonExistentFiles:
|
||||
class TestAngularUndefinedSymbols:
|
||||
"""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:
|
||||
"""Cursor on the ``import`` keyword (line 0, col 0 of app.component.ts)
|
||||
has no definition target — the TS companion returns ``[]``.
|
||||
@@ -148,7 +148,7 @@ class TestAngularUndefinedSymbols:
|
||||
result = language_server.request_definition(TS_FILE, 0, 0)
|
||||
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:
|
||||
"""A locally-scoped private field has at most its declaration plus its
|
||||
in-method use as references. ``defaultName`` on GreetingService is
|
||||
@@ -168,7 +168,7 @@ class TestAngularUndefinedSymbols:
|
||||
class TestAngularEdgeCasePositions:
|
||||
"""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:
|
||||
"""Line 0 of app.component.ts is an ``import`` statement, outside any
|
||||
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)
|
||||
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:
|
||||
"""Position (0, 0) is on the ``import`` keyword — no references."""
|
||||
result = language_server.request_references(TS_FILE, 0, 0)
|
||||
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:
|
||||
"""Position (0, 0) is on the ``import`` keyword — no definition."""
|
||||
result = language_server.request_definition(TS_FILE, 0, 0)
|
||||
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:
|
||||
"""An Angular template has no class/function containers; the HTML
|
||||
companion's documentSymbol provides element symbols only.
|
||||
@@ -198,7 +198,7 @@ class TestAngularEdgeCasePositions:
|
||||
result = language_server.request_containing_symbol(TEMPLATE_FILE, 1, 4)
|
||||
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:
|
||||
"""Lines 5 and 10 of app.component.ts are inside the AppComponent class
|
||||
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 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:
|
||||
"""Line 0 (import) and line 15 (blank/whitespace inside file but
|
||||
outside any symbol's range, depending on file structure) report no
|
||||
@@ -223,7 +223,7 @@ class TestAngularEdgeCasePositions:
|
||||
class TestAngularReferenceEdgeCases:
|
||||
"""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:
|
||||
"""Unlike ``request_references`` (which returns ``[]``),
|
||||
``request_referencing_symbols`` validates more strictly and surfaces
|
||||
@@ -241,7 +241,7 @@ class TestAngularReferenceEdgeCases:
|
||||
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:
|
||||
"""``request_defining_symbol`` short-circuits when no definition is
|
||||
found and returns None — no exception, even for negative positions.
|
||||
@@ -328,7 +328,7 @@ class TestAngularStartupCleanup:
|
||||
|
||||
import psutil
|
||||
|
||||
ls = _create_ls(Language.ANGULAR)
|
||||
ls = _create_ls(LanguageServerId.ANGULAR)
|
||||
my_proc = psutil.Process(os.getpid())
|
||||
children_before = {p.pid for p in my_proc.children(recursive=True)}
|
||||
|
||||
|
||||
@@ -11,24 +11,26 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
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
|
||||
class TestAnsibleLanguageServerBasics:
|
||||
"""Test basic Ansible language server functionality."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.ANSIBLE], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Language server starts and points to the correct repo."""
|
||||
assert language_server.is_running()
|
||||
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:
|
||||
"""Hover on ansible.builtin.package returns module documentation."""
|
||||
# playbook.yml line 10 (0-indexed): "ansible.builtin.package:"
|
||||
@@ -43,7 +45,7 @@ class TestAnsibleLanguageServerBasics:
|
||||
hover_text = str(hover_value)
|
||||
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:
|
||||
"""Completions at a task keyword position return Ansible module names."""
|
||||
# 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]}"
|
||||
|
||||
@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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -8,7 +8,7 @@ like request_document_symbols using the bash test repository.
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name,
|
||||
class TestBashLanguageServerBasics:
|
||||
"""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:
|
||||
"""Test that bash language server can be initialized successfully."""
|
||||
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:
|
||||
"""Test request_document_symbols for bash files."""
|
||||
# Test getting symbols from main.sh
|
||||
@@ -38,7 +38,7 @@ class TestBashLanguageServerBasics:
|
||||
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)}"
|
||||
|
||||
@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:
|
||||
"""Test request_document_symbols with body extraction."""
|
||||
# Test with include_body=True
|
||||
@@ -55,7 +55,7 @@ class TestBashLanguageServerBasics:
|
||||
assert "function greet_user()" in body, "Function body should contain function definition"
|
||||
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:
|
||||
"""Test function detection in utils.sh file."""
|
||||
# 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)}"
|
||||
|
||||
@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:
|
||||
"""Test that LSP detects different bash function syntax patterns correctly."""
|
||||
# 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(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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.bash
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -5,32 +5,34 @@ from unittest import mock
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
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
|
||||
|
||||
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
|
||||
class TestBSLLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Language server starts and attaches to the test repository."""
|
||||
assert language_server.is_running()
|
||||
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:
|
||||
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"
|
||||
|
||||
@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:
|
||||
doc_symbols = language_server.request_document_symbols("CommonModule.bsl")
|
||||
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}"
|
||||
|
||||
@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:
|
||||
"""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")
|
||||
@@ -50,7 +52,7 @@ class TestBSLLanguageServer:
|
||||
"ПолучитьСостояние 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:
|
||||
# CommonModule.bsl (0-indexed):
|
||||
# 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", "")]
|
||||
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:
|
||||
# CommonModule.bsl (0-indexed):
|
||||
# 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_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:
|
||||
"""``request_references`` from the declaration must include the cross-module call-site."""
|
||||
# ОбщийМодуль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", "")]
|
||||
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:
|
||||
"""``request_definition`` from a cross-module call-site must resolve to the other module."""
|
||||
# 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", "")]
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed = [s for s in all_symbols if has_malformed_name(s)]
|
||||
@@ -133,15 +135,15 @@ class TestBSLLanguageServer:
|
||||
|
||||
|
||||
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("script.os")
|
||||
assert not matcher.is_relevant_filename("module.py")
|
||||
|
||||
|
||||
def test_bsl_enum_registration() -> None:
|
||||
assert Language.BSL.value == "bsl"
|
||||
assert Language.BSL.get_ls_class().__name__ == "BSLLanguageServer"
|
||||
assert LanguageServerId.BSL.value == "bsl"
|
||||
assert LanguageServerId.BSL.get_ls_class().__name__ == "BSLLanguageServer"
|
||||
|
||||
|
||||
def test_bsl_dependency_provider_default_version() -> None:
|
||||
@@ -153,7 +155,7 @@ def test_bsl_dependency_provider_default_version() -> None:
|
||||
|
||||
settings = SolidLSPSettings()
|
||||
provider = BSLLanguageServer.DependencyProvider(
|
||||
settings.get_ls_specific_settings(Language.BSL),
|
||||
settings.get_ls_specific_settings(LanguageServerId.BSL),
|
||||
"/tmp/ls_resources",
|
||||
)
|
||||
|
||||
@@ -174,9 +176,9 @@ def test_bsl_dependency_provider_custom_version_no_sha() -> None:
|
||||
from solidlsp.language_servers.common import RuntimeDependencyCollection
|
||||
|
||||
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(
|
||||
settings.get_ls_specific_settings(Language.BSL),
|
||||
settings.get_ls_specific_settings(LanguageServerId.BSL),
|
||||
"/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
|
||||
|
||||
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(
|
||||
settings.get_ls_specific_settings(Language.BSL),
|
||||
settings.get_ls_specific_settings(LanguageServerId.BSL),
|
||||
"/tmp/ls_resources",
|
||||
)
|
||||
|
||||
@@ -241,9 +243,9 @@ def test_bsl_launch_command_requires_java() -> None:
|
||||
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
|
||||
|
||||
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(
|
||||
settings.get_ls_specific_settings(Language.BSL),
|
||||
settings.get_ls_specific_settings(LanguageServerId.BSL),
|
||||
"/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
|
||||
|
||||
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(
|
||||
settings.get_ls_specific_settings(Language.BSL),
|
||||
settings.get_ls_specific_settings(LanguageServerId.BSL),
|
||||
"/tmp/ls_resources",
|
||||
)
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
|
||||
@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:
|
||||
@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:
|
||||
"""bsl-language-server must flag the unterminated string literal in the fixture."""
|
||||
assert_file_diagnostics(
|
||||
|
||||
@@ -2,18 +2,18 @@ import pytest
|
||||
|
||||
from serena.project import Project
|
||||
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 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 . 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
|
||||
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):
|
||||
"""
|
||||
Test finding definition of 'greet' function call in core.clj
|
||||
@@ -27,7 +27,7 @@ class TestLanguageServerBasics:
|
||||
assert definition["relativePath"] == CORE_PATH
|
||||
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):
|
||||
"""
|
||||
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"
|
||||
|
||||
@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):
|
||||
with language_server.open_file(UTILS_PATH):
|
||||
# After "core/" in calculate-area
|
||||
@@ -53,7 +53,7 @@ class TestLanguageServerBasics:
|
||||
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/'"
|
||||
|
||||
@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):
|
||||
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:
|
||||
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):
|
||||
"""Test hover on greet function"""
|
||||
result = language_server.request_hover(CORE_PATH, 2, 7)
|
||||
@@ -82,7 +82,7 @@ class TestLanguageServerBasics:
|
||||
else:
|
||||
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):
|
||||
# Search for functions containing "add"
|
||||
result = language_server.request_workspace_symbol("add")
|
||||
@@ -93,7 +93,7 @@ class TestLanguageServerBasics:
|
||||
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}"
|
||||
|
||||
@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):
|
||||
"""Test definition lookup for core/greet usage in utils.clj"""
|
||||
# Position of 'greet' in core/greet call
|
||||
@@ -105,7 +105,7 @@ class TestLanguageServerBasics:
|
||||
definition = result[0]
|
||||
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):
|
||||
"""Test references to multiply function with content"""
|
||||
references = language_server.request_references(CORE_PATH, 12, 6)
|
||||
@@ -130,7 +130,7 @@ class TestLanguageServerBasics:
|
||||
utils_content = utils_refs[0].to_display_string()
|
||||
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):
|
||||
"""Test retrieving the full symbol tree for project overview
|
||||
We just check that we find some expected symbols.
|
||||
@@ -172,7 +172,7 @@ class TestLanguageServerBasics:
|
||||
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):
|
||||
"""Test finding symbols that reference a given symbol
|
||||
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}"
|
||||
|
||||
|
||||
@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
|
||||
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):
|
||||
"""Test retrieving content around specific lines"""
|
||||
# Test retrieving content around the greet function definition (line 2)
|
||||
@@ -209,7 +209,7 @@ class TestProjectBasics:
|
||||
content_str = result.to_display_string()
|
||||
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:
|
||||
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]
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -3,13 +3,13 @@ import os
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.clojure
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -19,8 +19,8 @@ import pytest
|
||||
|
||||
from serena.util.text_utils import find_text_coordinates
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.conftest import read_repo_file
|
||||
|
||||
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")
|
||||
|
||||
|
||||
@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
|
||||
class TestClojureProjectIndexing:
|
||||
"""Covers the "indexing leaks through results" bug for clojure-lsp.
|
||||
@@ -41,7 +41,7 @@ class TestClojureProjectIndexing:
|
||||
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:
|
||||
# locate the definition of `multiply` in core.clj without hardcoding coords
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""Mirrors the real-world penpot bug repro: ``multiply`` is defined in one
|
||||
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."
|
||||
)
|
||||
|
||||
@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:
|
||||
core_content = read_repo_file(language_server, CORE_PATH)
|
||||
coords = find_text_coordinates(core_content, r"\(defn (multiply)\b", require_unique=True)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from pathlib import Path
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
||||
|
||||
PYTHON_BACKEND_LANGUAGES = [
|
||||
Language.PYTHON,
|
||||
Language.PYTHON_TY,
|
||||
Language.PYTHON_PYREFLY,
|
||||
Language.PYTHON_BASEDPYRIGHT,
|
||||
LanguageServerId.PYTHON,
|
||||
LanguageServerId.PYTHON_TY,
|
||||
LanguageServerId.PYTHON_PYREFLY,
|
||||
LanguageServerId.PYTHON_BASEDPYRIGHT,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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:
|
||||
with start_ls_context(Language.CPP) as ccls:
|
||||
with start_ls_context(LanguageServerId.CPP) as ccls:
|
||||
path = os.path.join(lang, unit)
|
||||
symbols = ccls.request_document_symbols(path).get_all_symbols_and_roots()
|
||||
symbols = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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:
|
||||
with start_ls_context(Language.CPP) as clangd:
|
||||
with start_ls_context(LanguageServerId.CPP) as clangd:
|
||||
path = os.path.join(lang, unit)
|
||||
symbols = clangd.request_document_symbols(path).get_all_symbols_and_roots()
|
||||
symbols = symbols[0] if symbols and isinstance(symbols[0], list) else symbols
|
||||
|
||||
@@ -14,18 +14,18 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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
|
||||
|
||||
_cpp_servers: list[Language] = [Language.CPP]
|
||||
if language_tests_enabled(Language.CPP_CCLS):
|
||||
_cpp_servers.append(Language.CPP_CCLS)
|
||||
_cpp_servers: list[LanguageServerId] = [LanguageServerId.CPP]
|
||||
if language_server_tests_enabled(LanguageServerId.CPP_CCLS):
|
||||
_cpp_servers.append(LanguageServerId.CPP_CCLS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("language", [Language.CPP, Language.CPP_CCLS])
|
||||
def test_source_fn_matcher_includes_ino(language: Language) -> None:
|
||||
@pytest.mark.parametrize("language", [LanguageServerId.CPP, LanguageServerId.CPP_CCLS])
|
||||
def test_source_fn_matcher_includes_ino(language: LanguageServerId) -> None:
|
||||
"""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.
|
||||
@@ -172,7 +172,7 @@ int use_add() {
|
||||
@pytest.mark.cpp
|
||||
class TestCppDocumentSymbolCache:
|
||||
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"
|
||||
shutil.copytree(fixture_path, target_path)
|
||||
return target_path
|
||||
@@ -180,7 +180,7 @@ class TestCppDocumentSymbolCache:
|
||||
def test_cache_invalidates_when_clangd_context_changes(self, tmp_path: Path) -> None:
|
||||
repo_path = self._copy_cpp_fixture(tmp_path)
|
||||
ls_settings_alt = {
|
||||
Language.CPP: {
|
||||
LanguageServerId.CPP: {
|
||||
"compile_commands_dir": ".serena-alt",
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,7 @@ class TestCppDocumentSymbolCache:
|
||||
assert ls._raw_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)
|
||||
|
||||
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()]
|
||||
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_caches_loaded_and_clean(ls_default_again)
|
||||
_ = ls_default_again.request_document_symbols(main_cpp)
|
||||
@@ -220,7 +220,7 @@ class TestCppDocumentSymbolCache:
|
||||
assert not ls_default_again._document_symbols_cache_is_modified
|
||||
|
||||
with start_ls_context(
|
||||
Language.CPP,
|
||||
LanguageServerId.CPP,
|
||||
repo_path=str(repo_path),
|
||||
ls_specific_settings=ls_settings_alt,
|
||||
solidlsp_dir=tmp_path,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.cpp
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -23,12 +23,12 @@ from collections.abc import Iterator
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import find_identifier_position, get_repo_path, start_ls_context
|
||||
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_ACTOR_H = os.path.join("Source", "TestGame", "AbilityActor.h")
|
||||
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")
|
||||
def language_server() -> Iterator[SolidLanguageServer]:
|
||||
"""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
|
||||
|
||||
|
||||
|
||||
@@ -14,25 +14,27 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
pytestmark = [
|
||||
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:
|
||||
"""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:
|
||||
"""Test that the language server starts successfully."""
|
||||
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:
|
||||
"""Test that document symbols are returned for the main file."""
|
||||
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 "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:
|
||||
"""Test that document symbols are returned for the utils file."""
|
||||
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")]
|
||||
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:
|
||||
"""Test that the full symbol tree contains expected symbols."""
|
||||
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, "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:
|
||||
"""Test that symbol names do not contain unexpected formatting characters."""
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
@@ -86,7 +88,7 @@ class TestCrystalDefinition:
|
||||
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:
|
||||
"""Test goto_definition for a symbol defined within the same file."""
|
||||
file_path = os.path.join("src", "main.cr")
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
pytestmark = [
|
||||
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:
|
||||
@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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -10,16 +10,16 @@ from solidlsp.language_servers.csharp_language_server import (
|
||||
breadth_first_file_scan,
|
||||
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_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
|
||||
|
||||
|
||||
@pytest.mark.csharp
|
||||
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:
|
||||
"""Test finding symbols in the 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, "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:
|
||||
"""Test getting document symbols from a C# file."""
|
||||
file_path = os.path.join("Program.cs")
|
||||
@@ -45,7 +45,7 @@ class TestCSharpLanguageServer:
|
||||
assert "Program" 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:
|
||||
"""Test finding references using symbol selection range."""
|
||||
file_path = os.path.join("Program.cs")
|
||||
@@ -65,7 +65,7 @@ class TestCSharpLanguageServer:
|
||||
"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:
|
||||
"""Test getting symbols from nested namespace."""
|
||||
file_path = os.path.join("Models", "Person.cs")
|
||||
@@ -89,7 +89,7 @@ class TestCSharpLanguageServer:
|
||||
assert "ToString" in symbol_names, "ToString 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:
|
||||
"""Test finding references to Calculator.Subtract method across files."""
|
||||
# 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)
|
||||
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:
|
||||
"""Test that hover information is available and includes type information."""
|
||||
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 "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:
|
||||
repo_path = get_repo_path(Language.CSHARP)
|
||||
repo_path = get_repo_path(LanguageServerId.CSHARP)
|
||||
pos = find_identifier_position(repo_path / "Services" / "IGreeter.cs", "FormatGreeting")
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
repo_path = get_repo_path(Language.CSHARP)
|
||||
repo_path = get_repo_path(LanguageServerId.CSHARP)
|
||||
pos = find_identifier_position(repo_path / "Services" / "IGreeter.cs", "FormatGreeting")
|
||||
assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture"
|
||||
|
||||
@@ -327,7 +327,7 @@ class TestCSharpSolutionProjectOpening:
|
||||
# Verify the file actually exists
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.csharp
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
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.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""The server starts and reports the expected repository root."""
|
||||
assert language_server.is_running()
|
||||
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:
|
||||
"""``schema.cue`` exposes its top-level definitions with the expected hierarchy."""
|
||||
# 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)
|
||||
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:
|
||||
"""The repository-wide symbol tree contains definitions from all three CUE files."""
|
||||
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"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""``#Person`` used in ``main.cue`` resolves to its definition in ``schema.cue``."""
|
||||
# main.cue line 3 (0-indexed): "alice: #Person & {"
|
||||
@@ -85,8 +85,8 @@ class TestCueLanguageServer:
|
||||
assert target["uri"].endswith("schema.cue")
|
||||
assert target["range"]["start"] == {"line": 3, "character": 0}
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""``#Person`` has references in ``lib.cue`` and ``main.cue`` beyond its declaration."""
|
||||
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 ("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("repo_path", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""``#BuildGreeting`` (defined in lib.cue) is referenced from main.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"
|
||||
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("repo_path", [Language.CUE], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""``defaultLocale`` (schema.cue) is used in main.cue."""
|
||||
schema_path = str(repo_path / "schema.cue")
|
||||
@@ -130,7 +130,7 @@ class TestCueLanguageServer:
|
||||
# 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)}"
|
||||
|
||||
@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:
|
||||
"""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
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
|
||||
from serena.util.text_utils import find_text_coordinates
|
||||
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_utils import SymbolUtils
|
||||
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
|
||||
class TestDartLanguageServer:
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test that the language server starts and stops successfully."""
|
||||
# The fixture already handles start and stop
|
||||
assert language_server.is_running()
|
||||
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("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding definition of a method within the same file."""
|
||||
# In lib/main.dart:
|
||||
@@ -43,8 +43,8 @@ class TestDartLanguageServer:
|
||||
# But language server may return different positions
|
||||
assert definition_location["range"]["start"]["line"] >= 0
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding definition across different files."""
|
||||
# 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["range"]["start"]["line"] >= 0
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding definition of a class method."""
|
||||
# In lib/main.dart:
|
||||
@@ -86,8 +86,8 @@ class TestDartLanguageServer:
|
||||
# Definition of power method should be around line 13 (0-indexed)
|
||||
assert 12 <= definition_location["range"]["start"]["line"] <= 16
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding references to a method within the same file."""
|
||||
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")]
|
||||
assert len(main_dart_references) >= 1
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding references across different files."""
|
||||
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")]
|
||||
assert len(main_dart_references) >= 1
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding definition of a constructor call."""
|
||||
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)
|
||||
assert 3 <= definition_location["range"]["start"]["line"] <= 7
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.DART], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.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:
|
||||
"""Test finding definition through imports."""
|
||||
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
|
||||
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:
|
||||
"""Test finding symbols in the 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, "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:
|
||||
"""Test finding references using symbol selection range."""
|
||||
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)"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""Test request_containing_symbol for a method."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -217,7 +217,7 @@ class TestDartLanguageServer:
|
||||
body = containing_symbol["body"].get_text()
|
||||
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:
|
||||
"""Test request_containing_symbol for a class."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -229,7 +229,7 @@ class TestDartLanguageServer:
|
||||
assert containing_symbol["name"] == "Calculator"
|
||||
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:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -241,7 +241,7 @@ class TestDartLanguageServer:
|
||||
assert containing_symbol["name"] == "add"
|
||||
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:
|
||||
"""Test request_defining_symbol for a variable usage."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -259,7 +259,7 @@ class TestDartLanguageServer:
|
||||
if defining_symbol.get("name") == "add":
|
||||
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:
|
||||
"""Test request_defining_symbol for an imported class/function."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -272,7 +272,7 @@ class TestDartLanguageServer:
|
||||
# Could be Function or Method depending on language server interpretation
|
||||
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:
|
||||
"""Test request_defining_symbol for a static class method."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -284,7 +284,7 @@ class TestDartLanguageServer:
|
||||
assert defining_symbol.get("name") == "power"
|
||||
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:
|
||||
"""Test getting document symbols from a Dart file."""
|
||||
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
|
||||
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:
|
||||
"""Test comprehensive referencing symbols functionality."""
|
||||
file_path = os.path.join("lib", "main.dart")
|
||||
@@ -340,7 +340,7 @@ class TestDartLanguageServer:
|
||||
assert "start" 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:
|
||||
"""Test symbol resolution across multiple files."""
|
||||
helper_file_path = os.path.join("lib", "helper.dart")
|
||||
@@ -363,7 +363,7 @@ class TestDartLanguageServer:
|
||||
for ref in main_dart_refs:
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
@@ -376,7 +376,7 @@ class TestDartLanguageServer:
|
||||
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:
|
||||
"""Test that document symbols return the full method body range, not just the identifier.
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.dart
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -10,20 +10,23 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
# 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:
|
||||
"""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):
|
||||
"""Test finding references to a function definition."""
|
||||
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)
|
||||
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):
|
||||
"""Test finding references to create_user function."""
|
||||
file_path = os.path.join("lib", "services.ex")
|
||||
@@ -77,7 +80,7 @@ class TestElixirBasic:
|
||||
assert references is not None
|
||||
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):
|
||||
"""Test finding symbols that reference a specific function."""
|
||||
file_path = os.path.join("lib", "models.ex")
|
||||
@@ -101,7 +104,7 @@ class TestElixirBasic:
|
||||
|
||||
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):
|
||||
"""Test that enumeration doesn't timeout (regression test)."""
|
||||
# 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()
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
@@ -139,7 +142,7 @@ class TestElixirBasic:
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -5,11 +5,14 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled, start_ls_context
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled, start_ls_context
|
||||
|
||||
# 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
|
||||
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.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@@ -81,7 +84,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
|
||||
@pytest.mark.slow
|
||||
@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:
|
||||
"""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.
|
||||
"""
|
||||
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
|
||||
root = ls.request_full_symbol_tree()[0]
|
||||
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)"
|
||||
|
||||
|
||||
@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):
|
||||
"""Test that default Elixir directories are ignored."""
|
||||
# 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)",
|
||||
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):
|
||||
"""Test that symbol tree excludes build and dependency directories."""
|
||||
symbol_tree = language_server.request_full_symbol_tree()
|
||||
|
||||
@@ -12,11 +12,14 @@ import pytest
|
||||
|
||||
from serena.project import Project
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
|
||||
# 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:
|
||||
@@ -42,7 +45,7 @@ class TestElixirIntegration:
|
||||
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"
|
||||
|
||||
@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):
|
||||
"""Test that symbols can be resolved across different files."""
|
||||
# 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
|
||||
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):
|
||||
"""Test that the language server understands Elixir module hierarchy."""
|
||||
models_file = os.path.join("lib", "models.ex")
|
||||
@@ -91,7 +94,7 @@ class TestElixirIntegration:
|
||||
|
||||
def test_file_extension_matching(self):
|
||||
"""Test that the Elixir language recognizes the correct file extensions."""
|
||||
language = Language.ELIXIR
|
||||
language = LanguageServerId.ELIXIR
|
||||
matcher = language.get_source_fn_matcher()
|
||||
|
||||
# Test Elixir file extensions
|
||||
@@ -110,7 +113,7 @@ class TestElixirIntegration:
|
||||
|
||||
|
||||
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):
|
||||
"""Test comprehensive symbol search across the entire project."""
|
||||
# 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]
|
||||
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):
|
||||
"""Test that the language server understands Elixir protocols and implementations."""
|
||||
# Search for protocol definitions
|
||||
|
||||
@@ -12,12 +12,15 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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
|
||||
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:
|
||||
@@ -26,7 +29,7 @@ class TestElixirLanguageServerSymbols:
|
||||
@pytest.mark.xfail(
|
||||
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:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
# Test for a position inside the create_user function
|
||||
@@ -54,7 +57,7 @@ class TestElixirLanguageServerSymbols:
|
||||
if "body" in containing_symbol:
|
||||
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:
|
||||
"""Test request_containing_symbol for a module."""
|
||||
# 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 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:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
# Test for a position inside a function which is inside a module
|
||||
@@ -104,7 +107,7 @@ class TestElixirLanguageServerSymbols:
|
||||
expected_names = ["create_user", "UserService"]
|
||||
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:
|
||||
"""Test request_containing_symbol for a position with no containing symbol."""
|
||||
# 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
|
||||
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:
|
||||
"""Test request_referencing_symbols for a struct."""
|
||||
# Test referencing symbols for User struct
|
||||
@@ -146,7 +149,7 @@ class TestElixirLanguageServerSymbols:
|
||||
# We expect some references from services.ex
|
||||
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:
|
||||
"""Test request_referencing_symbols for a position with no symbol."""
|
||||
file_path = os.path.join("lib", "services.ex")
|
||||
@@ -164,7 +167,7 @@ class TestElixirLanguageServerSymbols:
|
||||
@pytest.mark.xfail(
|
||||
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:
|
||||
"""Test request_defining_symbol for a function call."""
|
||||
# Find a place where User.new is called in services.ex
|
||||
@@ -191,7 +194,7 @@ class TestElixirLanguageServerSymbols:
|
||||
@pytest.mark.xfail(
|
||||
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:
|
||||
"""Test request_defining_symbol for a struct usage."""
|
||||
# Find a place where User struct is used in services.ex
|
||||
@@ -215,7 +218,7 @@ class TestElixirLanguageServerSymbols:
|
||||
@pytest.mark.xfail(
|
||||
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:
|
||||
"""Test request_defining_symbol for a position with no symbol."""
|
||||
# Test for a position with no symbol (e.g., whitespace or comment)
|
||||
@@ -226,7 +229,7 @@ class TestElixirLanguageServerSymbols:
|
||||
# Should return None or empty
|
||||
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:
|
||||
"""Test integration between different symbol methods."""
|
||||
file_path = os.path.join("lib", "models.ex")
|
||||
@@ -257,7 +260,7 @@ class TestElixirLanguageServerSymbols:
|
||||
assert isinstance(refs, list)
|
||||
|
||||
@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:
|
||||
"""Test that symbol tree structure is correctly built."""
|
||||
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]
|
||||
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:
|
||||
"""Test request_dir_overview functionality."""
|
||||
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]
|
||||
# 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:
|
||||
"""Test containing symbol for module attributes."""
|
||||
file_path = os.path.join("lib", "models.ex")
|
||||
|
||||
@@ -3,16 +3,16 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
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:
|
||||
symbols = language_server.request_full_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, "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:
|
||||
file_path = os.path.join("Main.elm")
|
||||
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"])
|
||||
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:
|
||||
# Test formatMessage function which is defined in Utils.elm and used in Main.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)
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -3,14 +3,14 @@ import shutil
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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")
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -8,24 +8,24 @@ like request_references using the test repository.
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
|
||||
@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:
|
||||
"""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:
|
||||
"""Test that the Erlang language server initializes properly."""
|
||||
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:
|
||||
"""Test document symbols retrieval for Erlang files."""
|
||||
try:
|
||||
@@ -43,7 +43,7 @@ class TestErlangLanguageServerBasics:
|
||||
else:
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
@@ -56,7 +56,7 @@ class TestErlangLanguageServerBasics:
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -4,13 +4,13 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import language_tests_enabled, start_ls_context
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled, start_ls_context
|
||||
|
||||
# These marks will be applied to all tests in this module
|
||||
pytestmark = [
|
||||
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]:
|
||||
"""Fixture to set up an LS for the erlang test repo with the 'ignored_dir' directory ignored."""
|
||||
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
|
||||
|
||||
|
||||
@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.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):
|
||||
"""Tests that request_full_symbol_tree ignores the configured directory."""
|
||||
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.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):
|
||||
"""Tests that find_references ignores the configured directory."""
|
||||
# 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.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:
|
||||
"""Tests that refs and symbols with glob patterns are ignored."""
|
||||
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
|
||||
root = ls.request_full_symbol_tree()[0]
|
||||
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)"
|
||||
|
||||
|
||||
@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):
|
||||
"""Test that default Erlang directories are ignored."""
|
||||
# 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"
|
||||
|
||||
|
||||
@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):
|
||||
"""Test that symbol tree excludes build and dependency directories."""
|
||||
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}"
|
||||
|
||||
|
||||
@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):
|
||||
"""Test that compiled Erlang 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"
|
||||
|
||||
|
||||
@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):
|
||||
"""Test that rebar-specific directories are ignored."""
|
||||
# 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"
|
||||
|
||||
|
||||
@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):
|
||||
"""Test that document symbols from ignored directories are not included."""
|
||||
# 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
|
||||
|
||||
|
||||
@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):
|
||||
"""Test Erlang-specific ignore patterns work correctly."""
|
||||
erlang_ignored_dirs = ["_build", "ebin", ".rebar3", "_checkouts", "cover"]
|
||||
|
||||
@@ -12,21 +12,21 @@ import os
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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
|
||||
pytestmark = [
|
||||
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:
|
||||
"""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:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
# Test for a position inside the create_user function
|
||||
@@ -53,7 +53,7 @@ class TestErlangLanguageServerSymbols:
|
||||
if "body" in containing_symbol:
|
||||
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:
|
||||
"""Test request_containing_symbol for a module."""
|
||||
# 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 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:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
# Test for a position inside a function which is inside a module
|
||||
@@ -107,7 +107,7 @@ class TestErlangLanguageServerSymbols:
|
||||
expected_names = ["create_user", "models"]
|
||||
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:
|
||||
"""Test request_containing_symbol for a position with no containing symbol."""
|
||||
# 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
|
||||
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:
|
||||
"""Test request_referencing_symbols for a record."""
|
||||
# Test referencing symbols for user record
|
||||
@@ -149,7 +149,7 @@ class TestErlangLanguageServerSymbols:
|
||||
# We expect some references from models.erl
|
||||
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:
|
||||
"""Test request_referencing_symbols for a 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
|
||||
|
||||
@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:
|
||||
"""Test request_referencing_symbols for a position with no symbol."""
|
||||
file_path = os.path.join("src", "models.erl")
|
||||
@@ -196,7 +196,7 @@ class TestErlangLanguageServerSymbols:
|
||||
pass
|
||||
|
||||
# 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:
|
||||
"""Test request_defining_symbol for a function call."""
|
||||
# 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"]:
|
||||
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:
|
||||
"""Test request_defining_symbol for a record usage."""
|
||||
# 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"]:
|
||||
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:
|
||||
"""Test request_defining_symbol for a module function call."""
|
||||
# Find a place where utils:validate_input is called
|
||||
@@ -264,7 +264,7 @@ class TestErlangLanguageServerSymbols:
|
||||
if defining_symbol:
|
||||
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:
|
||||
"""Test request_defining_symbol for a position with no symbol."""
|
||||
# Test for a position with no symbol (e.g., whitespace or comment)
|
||||
@@ -275,7 +275,7 @@ class TestErlangLanguageServerSymbols:
|
||||
# Should return None or empty
|
||||
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:
|
||||
"""Test integration between different symbol methods."""
|
||||
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.",
|
||||
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:
|
||||
"""Test that symbol tree structure is correctly built."""
|
||||
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)]
|
||||
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:
|
||||
"""Test request_dir_overview functionality."""
|
||||
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]
|
||||
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:
|
||||
"""Test containing symbol for record field access."""
|
||||
file_path = os.path.join("src", "models.erl")
|
||||
@@ -378,7 +378,7 @@ class TestErlangLanguageServerSymbols:
|
||||
expected_names = ["create_user", "update_user", "format_user_info"]
|
||||
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:
|
||||
"""Test containing symbol for function specs."""
|
||||
file_path = os.path.join("src", "models.erl")
|
||||
@@ -410,7 +410,7 @@ class TestErlangLanguageServerSymbols:
|
||||
"Similar to known Next LS timeout issues.",
|
||||
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:
|
||||
"""Test finding references across different files."""
|
||||
# Test that we can find references to models module functions in services.erl
|
||||
|
||||
@@ -8,10 +8,10 @@ Note: These tests require fortls to be installed: pip install fortls
|
||||
import pytest
|
||||
|
||||
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_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
|
||||
|
||||
# Mark all tests in this module as fortran tests
|
||||
@@ -21,7 +21,7 @@ pytestmark = pytest.mark.fortran
|
||||
class TestFortranLanguageServer:
|
||||
"""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:
|
||||
"""Test finding symbols using request_full_symbol_tree."""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
@@ -39,11 +39,11 @@ class TestFortranLanguageServer:
|
||||
# Verify subroutine symbol
|
||||
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:
|
||||
repo_path = get_repo_path(Language.FORTRAN)
|
||||
repo_path = get_repo_path(LanguageServerId.FORTRAN)
|
||||
pos = find_identifier_position(repo_path / "modules" / "geometry.f90", "distance")
|
||||
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 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:
|
||||
repo_path = get_repo_path(Language.FORTRAN)
|
||||
repo_path = get_repo_path(LanguageServerId.FORTRAN)
|
||||
pos = find_identifier_position(repo_path / "modules" / "geometry.f90", "distance")
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""Test that document symbols can be retrieved from Fortran files."""
|
||||
# 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 "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:
|
||||
"""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]}"
|
||||
)
|
||||
|
||||
@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:
|
||||
"""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)
|
||||
@@ -142,7 +142,7 @@ class TestFortranLanguageServer:
|
||||
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:
|
||||
"""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
|
||||
# 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:
|
||||
"""Test finding the defining symbol - Serena's high-level API.
|
||||
|
||||
@@ -218,7 +218,7 @@ class TestFortranLanguageServer:
|
||||
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}"
|
||||
|
||||
@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:
|
||||
"""Test finding the containing symbol for a position in the code."""
|
||||
# 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 "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:
|
||||
"""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
|
||||
# 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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.fortran
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -5,23 +5,26 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from test.conftest import (
|
||||
find_identifier_position,
|
||||
get_repo_path,
|
||||
language_has_verified_implementation_support,
|
||||
language_tests_enabled,
|
||||
language_server_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.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
# 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:
|
||||
@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:
|
||||
"""Test finding symbols in the 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, "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:
|
||||
"""Test getting document symbols from the main Program.fs file."""
|
||||
file_path = os.path.join("Program.fs")
|
||||
@@ -45,11 +48,11 @@ class TestFSharpLanguageServer:
|
||||
symbol_names = [s.get("name") for s in 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:
|
||||
repo_path = get_repo_path(Language.FSHARP)
|
||||
repo_path = get_repo_path(LanguageServerId.FSHARP)
|
||||
pos = find_identifier_position(repo_path / "Formatter.fs", "FormatGreeting")
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
repo_path = get_repo_path(Language.FSHARP)
|
||||
repo_path = get_repo_path(LanguageServerId.FSHARP)
|
||||
pos = find_identifier_position(repo_path / "Formatter.fs", "FormatGreeting")
|
||||
assert pos is not None, "Could not find IGreeter.FormatGreeting in fixture"
|
||||
|
||||
@@ -72,7 +75,7 @@ class TestFSharpLanguageServer:
|
||||
for symbol in 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:
|
||||
"""Test getting document symbols from Calculator.fs file."""
|
||||
file_path = os.path.join("Calculator.fs")
|
||||
@@ -85,7 +88,7 @@ class TestFSharpLanguageServer:
|
||||
for expected in expected_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:
|
||||
"""Test finding references using symbol selection range."""
|
||||
file_path = os.path.join("Calculator.fs")
|
||||
@@ -108,7 +111,7 @@ class TestFSharpLanguageServer:
|
||||
# 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"
|
||||
|
||||
@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:
|
||||
"""Test getting symbols from nested Models namespace."""
|
||||
file_path = os.path.join("Models", "Person.fs")
|
||||
@@ -121,7 +124,7 @@ class TestFSharpLanguageServer:
|
||||
for expected in expected_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:
|
||||
"""Test finding references to Calculator functions across files."""
|
||||
# Find the subtract function in Calculator.fs
|
||||
@@ -143,7 +146,7 @@ class TestFSharpLanguageServer:
|
||||
# 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"
|
||||
|
||||
@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:
|
||||
"""Test go-to-definition functionality."""
|
||||
# Test going to definition of 'add' function from Program.fs
|
||||
@@ -156,7 +159,7 @@ class TestFSharpLanguageServer:
|
||||
# We should get at least some definitions
|
||||
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:
|
||||
"""Test hover information functionality."""
|
||||
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
|
||||
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:
|
||||
"""Test code completion functionality."""
|
||||
file_path = os.path.join("Program.fs")
|
||||
@@ -197,7 +200,7 @@ class TestFSharpLanguageServer:
|
||||
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
@@ -206,7 +209,7 @@ class TestFSharpLanguageServer:
|
||||
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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -5,22 +5,22 @@ import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.go
|
||||
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:
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoStruct"), "DemoStruct not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
|
||||
def test_find_symbol_matches_go_method_by_bare_name(self, language_server: SolidLanguageServer) -> None:
|
||||
symbols = language_server.request_full_symbol_tree(within_relative_path="main.go")
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestGoLanguageServer:
|
||||
assert bare_name_matches, "Expected a Go method to match by bare name"
|
||||
assert all(match.name == "Value" for match in bare_name_matches)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
|
||||
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("main.go")
|
||||
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"])
|
||||
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:
|
||||
"""
|
||||
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 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:
|
||||
repo_path = get_repo_path(Language.GO)
|
||||
repo_path = get_repo_path(LanguageServerId.GO)
|
||||
pos = find_identifier_position(repo_path / "main.go", "FormatGreeting")
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
repo_path = get_repo_path(Language.GO)
|
||||
repo_path = get_repo_path(LanguageServerId.GO)
|
||||
pos = find_identifier_position(repo_path / "main.go", "FormatGreeting")
|
||||
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
|
||||
|
||||
fixture_path = get_repo_path(Language.GO)
|
||||
fixture_path = get_repo_path(LanguageServerId.GO)
|
||||
target_path = tmp_path / "test_repo"
|
||||
|
||||
shutil.copytree(fixture_path, target_path)
|
||||
@@ -145,7 +145,7 @@ class TestGoBuildTags:
|
||||
|
||||
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")
|
||||
xfoo_symbols = ls.request_workspace_symbol("XFoo")
|
||||
|
||||
@@ -162,14 +162,14 @@ class TestGoBuildTags:
|
||||
repo_path = self._copy_go_fixture(tmp_path)
|
||||
|
||||
ls_settings = {
|
||||
Language.GO: {
|
||||
LanguageServerId.GO: {
|
||||
"gopls_settings": {
|
||||
"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")
|
||||
xnotfoo_symbols = ls.request_workspace_symbol("XNotFoo")
|
||||
|
||||
@@ -188,7 +188,7 @@ class TestGoBuildTags:
|
||||
repo_path = self._copy_go_fixture(tmp_path)
|
||||
|
||||
ls_settings_foo = {
|
||||
Language.GO: {
|
||||
LanguageServerId.GO: {
|
||||
"gopls_settings": {
|
||||
"buildFlags": ["-tags=foo"],
|
||||
},
|
||||
@@ -214,7 +214,7 @@ class TestGoBuildTags:
|
||||
assert ls._document_symbols_cache_is_modified
|
||||
|
||||
# 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)
|
||||
|
||||
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.
|
||||
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_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.
|
||||
with start_ls_context(
|
||||
Language.GO,
|
||||
LanguageServerId.GO,
|
||||
repo_path=str(repo_path),
|
||||
ls_specific_settings=ls_settings_foo,
|
||||
solidlsp_dir=tmp_path,
|
||||
@@ -277,7 +277,7 @@ class TestGoBuildTags:
|
||||
# A cache miss should repopulate and mark caches modified.
|
||||
_assert_caches_modified(ls_foo)
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.GO], indirect=True)
|
||||
def test_bare_symbol_names(self, language_server) -> None:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.go
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
|
||||
from serena.constants import SERENA_MANAGED_DIR_NAME
|
||||
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.settings import SolidLSPSettings
|
||||
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
|
||||
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)
|
||||
solidlsp_settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(Path.home() / ".serena"),
|
||||
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)
|
||||
|
||||
@@ -16,18 +16,20 @@ Test Repository Structure:
|
||||
import pytest
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
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.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
|
||||
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):
|
||||
"""
|
||||
Test precise symbol discovery in Calculator.hs.
|
||||
@@ -66,7 +68,7 @@ class TestHaskellLanguageServer:
|
||||
23,
|
||||
], 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):
|
||||
"""
|
||||
Test precise symbol discovery in Helper.hs.
|
||||
@@ -93,7 +95,7 @@ class TestHaskellLanguageServer:
|
||||
extra = symbol_names - expected_symbols - {"Helper"}
|
||||
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):
|
||||
"""
|
||||
Test that Main.hs properly references both Calculator and Helper modules.
|
||||
@@ -106,7 +108,7 @@ class TestHaskellLanguageServer:
|
||||
# Main.hs should have the 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):
|
||||
"""
|
||||
Test cross-file reference tracking for validateNumber function.
|
||||
@@ -132,7 +134,7 @@ class TestHaskellLanguageServer:
|
||||
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):
|
||||
"""
|
||||
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}"
|
||||
)
|
||||
|
||||
@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):
|
||||
"""
|
||||
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}"
|
||||
)
|
||||
|
||||
@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):
|
||||
"""
|
||||
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}"
|
||||
)
|
||||
|
||||
@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):
|
||||
"""
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
@@ -242,7 +244,7 @@ class TestHaskellLanguageServer:
|
||||
]
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import pytest
|
||||
|
||||
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_utils import SymbolUtils
|
||||
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
|
||||
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:
|
||||
"""Test that the Haxe language server starts successfully."""
|
||||
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:
|
||||
symbols = language_server.request_full_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, "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:
|
||||
"""Test that symbol names do not contain unexpected formatting characters."""
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
@@ -40,7 +40,7 @@ class TestHaxeLanguageServer:
|
||||
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:
|
||||
file_path = os.path.join("src", "Main.hx")
|
||||
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}"
|
||||
)
|
||||
|
||||
@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:
|
||||
# Test addNumbers which is defined in Helper.hx and used in Main.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"]
|
||||
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:
|
||||
file_path = os.path.join("src", "Main.hx")
|
||||
result = language_server.request_document_symbols(file_path)
|
||||
@@ -139,14 +139,14 @@ class TestHaxeLanguageServer:
|
||||
SymbolKind.Property,
|
||||
), 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:
|
||||
result = language_server.request_workspace_symbol("Helper")
|
||||
assert result is not None, "Workspace symbol search returned None"
|
||||
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}"
|
||||
|
||||
@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:
|
||||
"""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")
|
||||
@@ -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", ""))]
|
||||
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:
|
||||
"""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")
|
||||
@@ -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", ""))]
|
||||
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:
|
||||
file_path = os.path.join("src", "Main.hx")
|
||||
result = language_server.request_document_symbols(file_path)
|
||||
@@ -190,7 +190,7 @@ class TestHaxeLanguageServer:
|
||||
hover_str = str(hover)
|
||||
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:
|
||||
"""Hovering on a class name should return hover info."""
|
||||
file_path = os.path.join("src", "Main.hx")
|
||||
@@ -206,7 +206,7 @@ class TestHaxeLanguageServer:
|
||||
hover_str = str(hover)
|
||||
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:
|
||||
file_path = os.path.join("src", "Main.hx")
|
||||
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 "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:
|
||||
"""Request completions after Helper. in calculateResult — should return Helper's static methods."""
|
||||
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]
|
||||
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:
|
||||
overview = language_server.request_document_overview(os.path.join("src", "Main.hx"))
|
||||
assert overview, "Document overview returned empty list"
|
||||
@@ -248,7 +248,7 @@ class TestHaxeLanguageServer:
|
||||
assert s.get("name"), "Symbol missing 'name' 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:
|
||||
"""Verify that rapid successive requests don't return empty results.
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.haxe
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
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_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
@@ -30,19 +30,19 @@ def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, n
|
||||
class TestHlslSymbols:
|
||||
"""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:
|
||||
"""VertexInput struct should appear in common.hlsl symbols."""
|
||||
symbol = _find_symbol_by_name(language_server, "common.hlsl", "VertexInput")
|
||||
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:
|
||||
"""SafeNormalize function should appear in common.hlsl."""
|
||||
symbol = _find_symbol_by_name(language_server, "common.hlsl", "SafeNormalize")
|
||||
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:
|
||||
"""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")
|
||||
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:
|
||||
"""CSMain kernel should appear in compute_test.hlsl."""
|
||||
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "CSMain")
|
||||
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:
|
||||
"""Full symbol tree should contain symbols from multiple files."""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
@@ -73,7 +73,7 @@ class TestHlslSymbols:
|
||||
class TestHlslDefinition:
|
||||
"""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:
|
||||
"""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]
|
||||
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:
|
||||
"""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.
|
||||
"""
|
||||
|
||||
@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:
|
||||
"""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:
|
||||
"""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:
|
||||
"""Hovering over SafeNormalize definition should return info.
|
||||
|
||||
@@ -150,7 +150,7 @@ class TestHlslHover:
|
||||
hover_text = _extract_hover_text(hover_info)
|
||||
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:
|
||||
"""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 "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:
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = []
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.hlsl
|
||||
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:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
|
||||
Loaded 100 of 180 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user