mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-03 07:13:33 +00:00
Add Ada / SPARK language support via AdaCore Ada Language Server (#1461)
Introduces Language.ADA backed by AdaCore's Ada Language Server (ALS), the standard LSP-compliant server for Ada — and SPARK, since both share the .ads/.adb file extensions and ALS distinguishes SPARK by source-level pragmas/aspects rather than by file type. ALS is auto-downloaded from AdaCore's GitHub releases (linux-x64/arm64, darwin-x64/arm64, win32-x64) using the LanguageServerDependencyProviderSinglePath pattern, with SHA256 verification per-platform. Users can override the downloaded binary by setting ls_specific_settings.ada.ls_path to a pre-installed ada_language_server (e.g. from Alire, GNAT Studio, or the VS Code Ada extension). Test repo is a minimal multi-file GNAT project (default.gpr, helper.ads, helper.adb, main.adb). Tests modelled on test_php_basic.py: ls_running, find_definition (within/across), find_references (within/across), find_symbol, document_symbols, hierarchical document symbols, bare-symbol-name sanity check.
This commit is contained in:
1 parent
ab98ea6762
commit
249f6b07f9
16 files changed
+454
-2
No files matched your search
@@ -25,6 +25,7 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Elixir (`elixir-tools/next-ls`): Fix deadlock in monorepo projects where `mix.exs` lives in a subdirectory. The server now searches immediate subdirectories when no `mix.exs` is found at the repository root. #1444
|
||||
- Java (`eclipse.jdt.ls`): Add upstream JDTLS mode for offline / restricted-network use. Setting both `jdtls_path` and `lombok_path` in `ls_specific_settings.java` makes Serena use an existing upstream JDTLS installation (e.g. `brew install jdtls`) and the system JDK 21+, skipping the ~500 MB vscode-java VSIX, Gradle, and IntelliCode downloads. New related setting `java_home` lets the user override the JDK used to launch JDTLS. Default behavior unchanged — the JDTLS workspace hash is preserved bit-for-bit for users on the default route, so existing project caches are reused without a one-time reindex; the launcher path is mixed into the hash only when `jdtls_path` is set, isolating upstream installations from the default workspace. #1415
|
||||
- Java (eclipse.jdt.ls): Lombok-generated methods (getters/setters, builder(), equals/hashCode/toString, etc.) are now included in symbol-based tools (find_symbol, get_symbols_overview, edits). Added lombok_show_generated setting (default: on) to toggle this. Updated bundled vscode-java to 1.54.0-923. Issue #1432.
|
||||
- Add **Ada / SPARK** support using AdaCore's [Ada Language Server](https://github.com/AdaCore/ada_language_server). Auto-downloads the official prebuilt ALS binary (linux-x64/arm64, darwin-x64/arm64, win32-x64). A single `ada` language covers both Ada and SPARK, since the server uses the same `.ads`/`.adb` files for both and distinguishes SPARK by source-level pragmas/aspects. Users can override the binary by setting `ls_specific_settings.ada.ls_path` to a pre-installed `ada_language_server` (e.g. from Alire, GNAT Studio, or the VS Code Ada extension).
|
||||
- Add **Angular** (experimental) via a dual-server architecture: `@angular/language-server` (ngserver) handles standalone `.html` template files, while a companion `typescript-language-server` with `@angular/language-service` loaded as a tsserver plugin handles all `.ts` operations including inline templates. Provides type-aware navigation between templates and component classes. Requires Node.js, npm, and `@angular/core` installed in the project (`npm install` in the project root). Subsumes `typescript`+`html` for `.ts`/`.html` files when active; SCSS is not subsumed.
|
||||
- Add **HTML** (experimental) using `vscode-html-language-server` from the `vscode-langservers-extracted` npm package. Provides in-file element/id symbols via documentSymbol; cross-file references are not meaningful for HTML. Also used as a companion server by the Angular LS for plain HTML documentSymbol support.
|
||||
- Add **SCSS / Sass / CSS** (experimental) using [some-sass-language-server](https://github.com/wkillerud/some-sass). Handles `.scss`, `.sass`, and `.css` through one server, with full `@use`/`@forward` workspace-wide go-to-definition and find-references for variables, mixins, and functions across Sass files. The `.css` path uses the same `vscode-css-languageservice` engine that powers the standalone CSS LS; CSS feature toggles default off upstream and are flipped on at startup so symbols, hover, completion, and syntax-level diagnostics work for plain CSS as well.
|
||||
|
||||
@@ -110,7 +110,7 @@ Serena incorporates a powerful abstraction layer for the integration of language
|
||||
The underlying language servers are typically open-source projects or at least freely available for use.
|
||||
|
||||
When using Serena's language server backend, we provide **support for over 40 programming languages**, including
|
||||
AL, Angular, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
|
||||
Ada / SPARK, AL, Angular, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
|
||||
|
||||
### The Serena JetBrains Plugin
|
||||
|
||||
|
||||
@@ -28,6 +28,13 @@ or at least freely available for use.
|
||||
We currently provide direct, out-of-the-box support for the programming languages listed below.
|
||||
Some languages require additional installations or setup steps, as noted.
|
||||
|
||||
* **Ada / SPARK**
|
||||
(uses AdaCore's [Ada Language Server (ALS)](https://github.com/AdaCore/ada_language_server),
|
||||
automatically downloaded; supports `.ads`, `.adb`, and `.ada` files;
|
||||
works best with a `.gpr` GNAT project file at the repository root;
|
||||
SPARK is handled by the same server transparently — set language `ada` for both.
|
||||
To use a pre-installed ALS (e.g. from Alire, GNAT Studio, or the VS Code Ada extension),
|
||||
set `ls_specific_settings.ada.ls_path`.)
|
||||
* **AL**
|
||||
* **Angular**
|
||||
(experimental; requires Node.js + npm, plus `npm install` having been run in the project root so that `@angular/core`
|
||||
|
||||
+2
-1
@@ -352,6 +352,7 @@ markers = [
|
||||
"html: language server running for HTML (vscode-html-language-server, used as companion by Angular)",
|
||||
"scss: language server running for SCSS / Sass / CSS (some-sass-language-server)",
|
||||
"angular: language server running for Angular (uses @angular/language-server + @angular/language-service tsserver plugin)",
|
||||
"ada: language server running for Ada / SPARK (uses AdaCore Ada Language Server)",
|
||||
]
|
||||
|
||||
[tool.codespell]
|
||||
@@ -359,4 +360,4 @@ markers = [
|
||||
skip = '.git*,*.svg,*.lock,*.min.*'
|
||||
check-hidden = true
|
||||
ignore-regex = '\.\w+'
|
||||
ignore-words-list = 'paket,EDN'
|
||||
ignore-words-list = 'paket,EDN,als'
|
||||
@@ -0,0 +1,227 @@
|
||||
"""
|
||||
Provides Ada / SPARK specific instantiation of the LanguageServer class using
|
||||
AdaCore's Ada Language Server (ALS).
|
||||
|
||||
ALS handles both Ada and SPARK code transparently — SPARK is identified by
|
||||
pragmas/aspects in source rather than file extension, so a single
|
||||
``Language.ADA`` covers both.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import threading
|
||||
from typing import cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Bumped on upgrades; install dir is namespaced by version so old caches stay valid.
|
||||
DEFAULT_ALS_VERSION = "2026.2.202604091"
|
||||
DEFAULT_ALS_SHA256_BY_PLATFORM = {
|
||||
"osx-arm64": "1d25ded29b6beafcb34c9d0084d52809d84577356e467bf92e8fef32fc4216c4",
|
||||
"osx-x64": "18d3277a25a6e08ce3ee7230c3e5ac20419d573cb8d8c883e7983f387a67d223",
|
||||
"linux-arm64": "65c57df715df90f7581ecd6a1d1884663376baaf38cedbab80d10060ff91b03d",
|
||||
"linux-x64": "2eb436a7c0e3740128cceaa15da9ff856fa75ef3fbb3e2e9d3a1bd17e15cb949",
|
||||
"win-x64": "bca024dc3643b2d91aebbb747398e2e6f183ad8cb2f149e24fe7a38cdb16ed8d",
|
||||
}
|
||||
|
||||
|
||||
def _als_sha(version: str, platform_key: str) -> str | None:
|
||||
if version == DEFAULT_ALS_VERSION:
|
||||
return DEFAULT_ALS_SHA256_BY_PLATFORM[platform_key]
|
||||
return None
|
||||
|
||||
|
||||
# AdaCore's tarballs are downloaded directly from github.com release URLs;
|
||||
# GitHub redirects asset downloads through release-assets/objects subdomains.
|
||||
ALS_ALLOWED_HOSTS = (
|
||||
"github.com",
|
||||
"release-assets.githubusercontent.com",
|
||||
"objects.githubusercontent.com",
|
||||
)
|
||||
|
||||
# Each AdaCore tarball nests the binary under integration/vscode/ada/{arch}/{os}/...
|
||||
# alongside any bundled shared libraries (e.g. libgmp on macOS).
|
||||
_BIN_RELPATH_BY_PLATFORM = {
|
||||
"osx-arm64": "integration/vscode/ada/arm64/darwin/ada_language_server",
|
||||
"osx-x64": "integration/vscode/ada/x64/darwin/ada_language_server",
|
||||
"linux-arm64": "integration/vscode/ada/arm64/linux/ada_language_server",
|
||||
"linux-x64": "integration/vscode/ada/x64/linux/ada_language_server",
|
||||
"win-x64": "integration/vscode/ada/x64/win32/ada_language_server.exe",
|
||||
}
|
||||
|
||||
# AdaCore uses "darwin"/"win32" in asset names; remap from Serena's platform ids.
|
||||
_ASSET_SUFFIX_BY_PLATFORM = {
|
||||
"osx-arm64": "darwin-arm64",
|
||||
"osx-x64": "darwin-x64",
|
||||
"linux-arm64": "linux-arm64",
|
||||
"linux-x64": "linux-x64",
|
||||
"win-x64": "win32-x64",
|
||||
}
|
||||
|
||||
|
||||
class AdaLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Ada / SPARK specific instantiation of the LanguageServer class
|
||||
using AdaCore's Ada Language Server.
|
||||
|
||||
You can pass the following entries in ``ls_specific_settings["ada"]``:
|
||||
- ls_path: Absolute path to a pre-installed ``ada_language_server``
|
||||
executable. If set, Serena does not download ALS and uses this
|
||||
binary directly. Useful for users who already have ALS available
|
||||
via Alire (``alr install ada_language_server``), GNAT Studio, or
|
||||
the Ada VS Code extension.
|
||||
- als_version: Override the pinned ALS version downloaded by Serena
|
||||
(default: the bundled Serena version). Setting this requires
|
||||
``ls_path`` since custom-version SHA256 sums are unknown.
|
||||
"""
|
||||
|
||||
ALS_ALLOWED_HOSTS = ALS_ALLOWED_HOSTS
|
||||
|
||||
# GNAT default output directories — pruned during workspace traversal.
|
||||
_IGNORED_DIRS = frozenset({"obj", "lib", ".obj", ".objects"})
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in self._IGNORED_DIRS
|
||||
|
||||
@classmethod
|
||||
def _runtime_dependencies(cls, version: str) -> RuntimeDependencyCollection:
|
||||
base_url = f"https://github.com/AdaCore/ada_language_server/releases/download/{version}"
|
||||
return RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="ada-language-server",
|
||||
description=f"Ada Language Server for {platform_key}",
|
||||
url=f"{base_url}/als-{version}-{_ASSET_SUFFIX_BY_PLATFORM[platform_key]}.tar.gz",
|
||||
platform_id=platform_key,
|
||||
archive_type="gztar",
|
||||
binary_name=_BIN_RELPATH_BY_PLATFORM[platform_key],
|
||||
sha256=_als_sha(version, platform_key),
|
||||
allowed_hosts=ALS_ALLOWED_HOSTS,
|
||||
)
|
||||
for platform_key in _BIN_RELPATH_BY_PLATFORM
|
||||
]
|
||||
)
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""Setup runtime dependencies for ALS and return the path to the executable."""
|
||||
als_version = self._custom_settings.get("als_version", DEFAULT_ALS_VERSION)
|
||||
deps = AdaLanguageServer._runtime_dependencies(als_version)
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
install_dir = os.path.join(self._ls_resources_dir, f"als-{als_version}")
|
||||
als_executable_path = deps.binary_path(install_dir)
|
||||
if not os.path.exists(als_executable_path):
|
||||
log.info(
|
||||
"Downloading and extracting ada_language_server from %s to %s",
|
||||
dependency.url,
|
||||
install_dir,
|
||||
)
|
||||
deps.install(install_dir)
|
||||
if not os.path.exists(als_executable_path):
|
||||
raise FileNotFoundError(f"Download failed? Could not find ada_language_server executable at {als_executable_path}")
|
||||
os.chmod(als_executable_path, 0o755)
|
||||
return als_executable_path
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
# ALS speaks LSP over stdio by default; no flags needed.
|
||||
return [core_path]
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates an AdaLanguageServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
None,
|
||||
"ada",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
def _get_initialize_params(self) -> InitializeParams:
|
||||
"""Returns the initialization params for the Ada Language Server."""
|
||||
repository_absolute_path = self.repository_root_path
|
||||
root_uri = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
result = {
|
||||
"processId": os.getpid(),
|
||||
"rootPath": repository_absolute_path,
|
||||
"rootUri": root_uri,
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": True,
|
||||
"workspaceEdit": {"documentChanges": True},
|
||||
"symbol": {"symbolKind": {"valueSet": list(range(1, 27))}},
|
||||
"workspaceFolders": True,
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
},
|
||||
"textDocument": {
|
||||
"synchronization": {"didSave": True, "dynamicRegistration": True},
|
||||
"definition": {"dynamicRegistration": True, "linkSupport": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"hierarchicalDocumentSymbolSupport": True,
|
||||
"symbolKind": {"valueSet": list(range(1, 27))},
|
||||
},
|
||||
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||
"publishDiagnostics": {"relatedInformation": True},
|
||||
},
|
||||
"general": {"positionEncodings": ["utf-16"]},
|
||||
},
|
||||
"initializationOptions": {},
|
||||
"trace": "off",
|
||||
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
|
||||
}
|
||||
return cast(InitializeParams, result)
|
||||
|
||||
def _start_server(self) -> None:
|
||||
"""Start the Ada Language Server process and complete the LSP handshake."""
|
||||
|
||||
def register_capability_handler(params: dict) -> None:
|
||||
return
|
||||
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info("LSP: window/logMessage: %s", msg)
|
||||
|
||||
def do_nothing(params: dict) -> None:
|
||||
return
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("window/showMessage", do_nothing)
|
||||
|
||||
log.info("Starting ada_language_server process")
|
||||
self.server.start()
|
||||
|
||||
initialize_params = self._get_initialize_params()
|
||||
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
capabilities = init_response["capabilities"]
|
||||
assert "textDocumentSync" in capabilities, "ALS must support textDocumentSync"
|
||||
assert "definitionProvider" in capabilities, "ALS must support textDocument/definition"
|
||||
assert "documentSymbolProvider" in capabilities, "ALS must support textDocument/documentSymbol"
|
||||
assert "referencesProvider" in capabilities, "ALS must support textDocument/references"
|
||||
|
||||
self.server.notify.initialized({})
|
||||
self.server_ready.set()
|
||||
@@ -110,6 +110,14 @@ class Language(str, Enum):
|
||||
Uses a custom LSP server based on pygls. Automatically sets up
|
||||
a virtual environment with pygls dependencies on first use.
|
||||
"""
|
||||
ADA = "ada"
|
||||
"""Ada / SPARK language server using AdaCore's Ada Language Server (ALS).
|
||||
Supports .ads (specs), .adb (bodies), and .ada files. Auto-downloads the
|
||||
ALS binary from AdaCore's GitHub releases. Works best with a .gpr GNAT
|
||||
project file at the repository root. SPARK files are handled transparently
|
||||
by the same server, since SPARK is distinguished by pragmas/aspects in
|
||||
source rather than by file extension.
|
||||
"""
|
||||
# Experimental or deprecated Language Servers
|
||||
TYPESCRIPT_VTS = "typescript_vts"
|
||||
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
|
||||
@@ -455,6 +463,8 @@ class Language(str, Enum):
|
||||
return FilenameMatcher(".yaml", ".yml")
|
||||
case self.MSL:
|
||||
return FilenameMatcher(".mrc")
|
||||
case self.ADA:
|
||||
return FilenameMatcher(".ads", ".adb", ".ada", case_sensitive=False)
|
||||
case self.HTML:
|
||||
return FilenameMatcher(".html", ".htm")
|
||||
case self.SCSS:
|
||||
@@ -702,6 +712,10 @@ class Language(str, Enum):
|
||||
from solidlsp.language_servers.msl_language_server import MslLanguageServer
|
||||
|
||||
return MslLanguageServer
|
||||
case self.ADA:
|
||||
from solidlsp.language_servers.ada_language_server import AdaLanguageServer
|
||||
|
||||
return AdaLanguageServer
|
||||
case self.HTML:
|
||||
from solidlsp.language_servers.vscode_html_language_server import VsCodeHtmlLanguageServer
|
||||
|
||||
|
||||
@@ -255,6 +255,7 @@ is_windows = platform.system() == "Windows"
|
||||
|
||||
|
||||
_LANGUAGE_PYTEST_MARKERS: dict[Language, list[MarkDecorator | Mark]] = {
|
||||
Language.ADA: [pytest.mark.ada],
|
||||
Language.CLOJURE: [
|
||||
pytest.mark.clojure,
|
||||
pytest.mark.skipif(not is_clojure_cli_available(), reason="clojure CLI is not installed"),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
obj/
|
||||
*.ali
|
||||
*.o
|
||||
@@ -0,0 +1,7 @@
|
||||
project Default is
|
||||
|
||||
for Source_Dirs use ("src");
|
||||
for Object_Dir use "obj";
|
||||
for Main use ("main.adb");
|
||||
|
||||
end Default;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Intentionally invalid Ada — exercises the LSP diagnostics path.
|
||||
-- The procedure body is missing the trailing `end Diagnostics_Sample;` token,
|
||||
-- which libadalang flags as a syntax error regardless of project setup.
|
||||
procedure Diagnostics_Sample is
|
||||
begin
|
||||
null
|
||||
@@ -0,0 +1,9 @@
|
||||
package body Helper is
|
||||
|
||||
function Greet (Name : String) return String is
|
||||
Prefix : constant String := "Hello, ";
|
||||
begin
|
||||
return Prefix & Name & "!";
|
||||
end Greet;
|
||||
|
||||
end Helper;
|
||||
@@ -0,0 +1,7 @@
|
||||
package Helper is
|
||||
|
||||
type Greeting_Style is (Friendly, Formal);
|
||||
|
||||
function Greet (Name : String) return String;
|
||||
|
||||
end Helper;
|
||||
@@ -0,0 +1,8 @@
|
||||
with Ada.Text_IO;
|
||||
with Helper;
|
||||
|
||||
procedure Main is
|
||||
Greeting : constant String := Helper.Greet ("Ada");
|
||||
begin
|
||||
Ada.Text_IO.Put_Line (Greeting);
|
||||
end Main;
|
||||
Whitespace-only changes.
@@ -0,0 +1,144 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.ada
|
||||
class TestAdaLanguageServer:
|
||||
"""Tests for AdaCore's Ada Language Server (covers Ada and SPARK).
|
||||
|
||||
Source layout (test/resources/repos/ada/test_repo):
|
||||
|
||||
default.gpr
|
||||
src/
|
||||
helper.ads package spec, declares ``Greet`` and ``Greeting_Style``
|
||||
helper.adb package body, defines ``Greet``
|
||||
main.adb ``with Helper;`` and calls ``Helper.Greet``
|
||||
|
||||
LSP positions are 0-indexed. Below, the layout is annotated with the
|
||||
columns used by each test so that future edits to the fixtures are
|
||||
obviously test-affecting.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
@pytest.mark.parametrize("repo_path", [Language.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)
|
||||
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");
|
||||
# 7/6: Ada.Text_IO.Put_Line (Greeting);
|
||||
# `Greeting` in `Put_Line (Greeting)` starts at column 25 on LSP line 6.
|
||||
main_path = str(repo_path / "src" / "main.adb")
|
||||
definitions = language_server.request_definition(main_path, 6, 26)
|
||||
|
||||
assert definitions, f"Expected non-empty definition list but got {definitions=}"
|
||||
assert len(definitions) == 1
|
||||
loc = definitions[0]
|
||||
assert loc["uri"].endswith("main.adb")
|
||||
# `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)
|
||||
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");`
|
||||
# ^ col 40
|
||||
# ALS resolves cross-file `Helper.Greet` to its spec declaration in helper.ads
|
||||
# (LSP line 4, column 12 — the `G` of `Greet` after ` function `).
|
||||
main_path = str(repo_path / "src" / "main.adb")
|
||||
definitions = language_server.request_definition(main_path, 4, 40)
|
||||
|
||||
assert definitions, f"Expected non-empty definition list but got {definitions=}"
|
||||
assert len(definitions) == 1
|
||||
loc = definitions[0]
|
||||
assert loc["uri"].endswith("helper.ads")
|
||||
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)
|
||||
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
|
||||
# at least one non-declaration usage in the same file. `Helper` is referenced as a
|
||||
# context clause on line 1 and again as the qualifier of `Helper.Greet` on line 4.
|
||||
main_path = str(repo_path / "src" / "main.adb")
|
||||
references = language_server.request_references(main_path, 1, 5)
|
||||
|
||||
assert references, f"Expected non-empty references for Helper but got {references=}"
|
||||
ref_lines_in_main = {loc["range"]["start"]["line"] for loc in references if loc["uri"].endswith("main.adb")}
|
||||
# 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)
|
||||
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;`
|
||||
# `Greet` starts at column 12 (` ` + `function ` = 3 + 9 = 12).
|
||||
spec_path = str(repo_path / "src" / "helper.ads")
|
||||
references = language_server.request_references(spec_path, 4, 12)
|
||||
|
||||
assert references, f"Expected non-empty references for Helper.Greet but got {references=}"
|
||||
ref_files = {loc["uri"].split("/")[-1] for loc in references}
|
||||
# The call site in main.adb must appear in the references.
|
||||
assert "main.adb" in ref_files, f"Expected reference in main.adb, got {ref_files}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper package not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Greet"), "Greet subprogram not found in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main procedure not found in symbol tree"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.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()
|
||||
names = {sym.get("name") for sym in all_symbols if sym.get("name")}
|
||||
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)
|
||||
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()
|
||||
|
||||
root_names = [s.get("name") for s in root_symbols]
|
||||
assert "Helper" in root_names, f"Helper package not at root level. Roots: {root_names}"
|
||||
|
||||
helper_symbol = next((s for s in root_symbols if s.get("name") == "Helper"), None)
|
||||
assert helper_symbol is not None, "Helper package missing from root symbols"
|
||||
helper_children = helper_symbol.get("children", [])
|
||||
helper_child_names = [c.get("name") for c in helper_children]
|
||||
assert helper_child_names, f"Helper package has no children — hierarchicalDocumentSymbolSupport is not working. Roots: {root_names}"
|
||||
assert "Greet" in helper_child_names, f"Greet not nested under Helper. Children: {helper_child_names}"
|
||||
|
||||
# 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)
|
||||
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
|
||||
# - dotted unit names like "Ada.Text_IO" — references to library packages
|
||||
# Both are legitimate names for Ada; allow whitespace and periods accordingly.
|
||||
all_symbols = request_all_symbols(language_server)
|
||||
malformed_symbols = [s for s in all_symbols if has_malformed_name(s, whitespace_allowed=True, period_allowed=True)]
|
||||
if malformed_symbols:
|
||||
pytest.fail(
|
||||
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
||||
pytrace=False,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
|
||||
@pytest.mark.ada
|
||||
class TestAdaDiagnostics:
|
||||
@pytest.mark.parametrize("language_server", [Language.ADA], indirect=True)
|
||||
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
"src/diagnostics_sample.adb",
|
||||
(),
|
||||
min_count=1,
|
||||
)
|
||||
Reference in new issue
Block a user