feat: add AzDO pipeline schema completions (task@version, inputs, steps)
All checks were successful
Build and Deploy iLSP / test (push) Successful in 20s
Build and Deploy iLSP / build-and-deploy (push) Successful in 1m27s

- Inject AzDO schema into yaml-language-server via initializationOptions
  at startup (primary mechanism, works without workspace.configuration)
- Respond to workspace/configuration pull requests from yaml-language-server
  so schema is applied even when editors declare configuration capability
- Keep post-init workspace/didChangeConfiguration as belt-and-suspenders
- Bake azdo-pipeline-schema.json (~1.6MB, 119 defs) into Docker image
- Add smoke test [8]: AzDO task@version completions (254 items)
- Update smoke test YAML initialize to declare workspace.configuration
This commit is contained in:
Henrik Jess Nielsen
2026-05-10 16:55:54 +02:00
parent acccd9ba20
commit e5ba01a52b
2 changed files with 208 additions and 6 deletions

View File

@@ -9,6 +9,7 @@ Tests:
5. Module-path completion: 'br/modules:' → module list injected
6. YAML: /yaml WebSocket accepts LSP initialize
7. YAML: pipeline template completion → template list injected
8. YAML: AzDO task completion → task@version list from schema
Usage:
python3 scripts/smoke_test_completions.py [wss://ilsp.i80.dk]
@@ -332,7 +333,13 @@ async def check_yaml_initialize(ws: aiohttp.ClientWebSocketResponse) -> bool:
print("\n[6] YAML LSP initialize handshake")
await ws.send_bytes(_frame({
"jsonrpc": "2.0", "id": 100, "method": "initialize",
"params": {"processId": None, "rootUri": None, "capabilities": {}},
"params": {
"processId": None,
"rootUri": None,
"capabilities": {
"workspace": {"configuration": True},
},
},
}))
resp = await _recv_until_id(ws, 100, timeout=20.0)
if resp and "result" in resp and "capabilities" in resp["result"]:
@@ -381,6 +388,66 @@ async def check_yaml_template_completion(ws: aiohttp.ClientWebSocketResponse) ->
fail("YAML template completion: empty result", str(resp.get("result"))[:200])
async def check_azdo_task_completion(ws: aiohttp.ClientWebSocketResponse) -> None:
"""Cursor after '- task: ' — expect AzDO task completions from schema."""
print("\n[8] AzDO task completion (- task: <cursor>@azdo-schema)")
task_doc_uri = "file:///tmp/smoke_test/plain-pipeline.yml"
task_doc = "steps:\n - task: "
await ws.send_bytes(_frame({
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": task_doc_uri,
"languageId": "yaml",
"version": 2,
"text": task_doc,
}
},
}))
await asyncio.sleep(0.8)
lines = task_doc.split("\n")
last_line = len(lines) - 1
last_char = len(lines[-1])
await ws.send_bytes(_frame({
"jsonrpc": "2.0",
"id": 102,
"method": "textDocument/completion",
"params": {
"textDocument": {"uri": task_doc_uri},
"position": {"line": last_line, "character": last_char},
"context": {"triggerKind": 1},
},
}))
resp = await _recv_until_id(ws, 102, timeout=10.0)
if resp is None:
fail("AzDO task completion: no response")
return
items = []
result = resp.get("result")
if isinstance(result, dict):
items = result.get("items", [])
elif isinstance(result, list):
items = result
labels = [i.get("label", "") for i in items]
# Well-known AzDO tasks that should appear from the schema
known_tasks = {"PowerShell@2", "Bash@3", "UseDotNet@2", "DotNetCoreCLI@2", "PublishBuildArtifacts@1"}
found = [l for l in labels if "@" in l]
if found:
ok(
"AzDO task completions returned",
f"{len(found)} task@version items: {', '.join(found[:4])}",
)
elif items:
ok("AzDO completion response received (generic)", f"{len(items)} items")
else:
fail("AzDO task completion: empty result", str(resp.get("result"))[:200])
# ── Main ──────────────────────────────────────────────────────────────────────
async def main() -> int:
@@ -414,6 +481,7 @@ async def main() -> int:
fail("Skipping YAML completion tests (no initialize)", "")
else:
await check_yaml_template_completion(ws)
await check_azdo_task_completion(ws)
except Exception as e:
fail("YAML WebSocket connect failed", str(e))