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:
Vitalijs Kuprins authored and GitHub committed 2026-05-09 17:49:12 +02:00
1 parent ab98ea6762
commit 249f6b07f9
16 files changed
+454 -2

No files matched your search

+1
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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()
+14
View File
@@ -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
+1
View File
@@ -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;
View File
Whitespace-only changes.
+144
View File
@@ -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,
)
+17
View File
@@ -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,
)