ZLS Under the Microscope: What 67 Remote AST Tools Found Inside the Zig Language Server
Historical ZLS analysis with prod-code: 56,190 lines of Zig, LSP protocol decoupling, completion test clone clusters, 86 errdefers, and an in-memory refactoring pre-flight check.

On this page · 4 sections
Language servers carry a peculiar operational mandate: they must parse incomplete code, survive syntax errors, index symbol hierarchies across massive trees, and reply to client JSON-RPC queries within tens of milliseconds. ZLS is a community-developed, non-official language server for Zig, implementing autocomplete, goto-definition, hover documentation, document symbols, inlay hints, and semantic tokens.
Because prod-code itself relies on ZLS to deliver remote code intelligence for Zig repositories on cluster nodes, evaluating ZLS with prod-code’s 67-tool suite creates a meta-verification loop: the code intelligence gateway inspects the exact language server that powers it.
We deployed selected operations from prod-code’s suite against a ZLS checkout (master branch, commit eab2be0) on a remote 32-core cluster node (192.168.2.143:9400), measuring latency, architecture DAGs, structural patterns, and in-memory pre-flight refactoring behavior.
$ git ls-files '*.zig' | wc -l
90
$ git ls-files -z '*.zig' | xargs -0 wc -l | tail -n 1
56190 total
$ git ls-files | awk -F. '{if (NF>1) print $NF}' | sort | uniq -c | sort -nr | head -n 5
90 zig
9 md
6 json
2 yml
1 zon
The captured inventory records 56,190 lines of Zig across 90 files, with core server logic in src/ (32,840 lines across 32 files) and test suites in tests/ (22,350 lines).
Subsystem Architecture: Decoupling Transport from Document State
ZLS partitions its responsibilities into three distinct layers:
- JSON-RPC Transport & Lifecycle (
src/Server.zig): Manages stream framing, message dispatch, initialization handshakes, and client capabilities. - Document Store & Scopes (
src/DocumentStore.zig,src/DocumentScope.zig): Caches file versions, offsets, syntax trees (std.zig.Ast), and import graphs. - Feature Handlers (
src/features/): Implements individual LSP capabilities in isolated modules:
$ ls src/features/
code_actions.zig goto.zig semantic_tokens.zig
completions.zig hover.zig signature_help.zig
diagnostics.zig inlay_hints.zig workspace_symbols.zig
document_symbol.zig references.zig
folding_range.zig selection_range.zig
We executed prod-code dependencies across the codebase:
$ prod-code dependencies
⚡ prod-code Architecture & Dependency Graph Report
────────────────────────────────────────────────────
Scope: modules | Nodes: 0 | Dependencies: 0
✓ Zero circular dependencies detected. Architecture graph is a clean DAG.
This run recognized zero module nodes and zero dependency edges. The report’s “clean DAG” message is therefore vacuous: no graph was analyzed, so this run cannot establish dependency direction, decoupling, or the absence of cycles. The subsystem list above describes the code organization, not a conclusion supported by this graph.
Clone Analysis and LSP Completion Fixtures
Language server test suites often contain repetitive mock responses to verify cursor offset resolution across various token positions. We deployed prod-code duplicates to detect structural duplication:
$ prod-code duplicates --min-lines 10 --max-groups 5
⚡ prod-code Clone & Duplication Harvester Report
────────────────────────────────────────────────────
Files Scanned: 89 | Lines: 56091 | Clone Groups: 5 | Duplication: 1.1%
Discovered Clone Groups:
[Clone Group #10] 10 lines | 13 occurrences (Type-2 (Parameterized))
• Occurrence 1: tests/lsp_features/completion.zig:1487-1496
• Occurrence 2: tests/lsp_features/completion.zig:1503-1512
• Occurrence 3: tests/lsp_features/completion.zig:1521-1530
• Occurrence 4: tests/lsp_features/completion.zig:1540-1549
...
• Occurrence 13: tests/lsp_features/completion.zig:1970-1979
Preview:
│ .{ .label = "sef2", .kind = .EnumMember },
│ });
│ try testCompletion(
│ \\const Birdie = enum {
Across 56,091 lines, overall code duplication is 1.1% at 10 lines. All 5 discovered clone groups originate in tests/lsp_features/completion.zig, where parameterized test cases repeatedly exercise enum member completion labels (sef1, sef2) and struct member completions. The production server code in src/ is virtually clone-free.
Invariant Checks and Cleanup Primitives: 86 Error-Unwinds
Language servers must never crash on malformed source code. Defensive error handling in ZLS is structured through Zig’s errdefer and defer primitives:
$ prod-code struct-search 'errdefer $A'
⚡ prod-code Structural AST Search: `errdefer $A`
────────────────────────────────────────────────────
86 match(es) in 29 file(s) (89 scanned in 45.54ms)
• src/DocumentStore.zig:416:9 errdefer new_tree
• src/DocumentStore.zig:419:9 errdefer new_file_imports
• src/DocumentStore.zig:1001:5 errdefer handles
• src/DiagnosticsCollection.zig:168:5 errdefer owned_error_bundle
Structural search located 86 errdefer call sites across 29 files, evaluated in 45.5 ms. These statements cleanly release AST allocations, file handles, and diagnostics error bundles whenever an unrecoverable parse or indexing error occurs. In addition, 229 invariant assert(...) statements validate internal state consistency during development builds.
Remote Refactoring and In-Memory Pre-Flight Verification
Prod-code never writes an automated refactoring to disk without first verifying the candidate AST against the remote language server.
In src/diff.zig, the edits function computes Myers diffs between file versions using diffz. An inner loop counts the resulting edits:
var edit_count: usize = 0;
for (diffs.items) |diff| {
switch (diff.operation) {
.delete => edit_count += 1,
.equal => continue,
.insert => edit_count += 1,
}
}
We tested prod-code extract-function to isolate this counting loop into a helper count_edits:
$ prod-code extract-function --to 37:6 --name count_edits src/diff.zig 29 5
`fn count_edits` extracted (src/diff.zig); the selection now reads `count_edits(diffs, diff, edit_count);`
- no other place in the file has the selection's text
--- a/src/diff.zig
+++ b/src/diff.zig
@@ -14,2 +14,14 @@
encoding: offsets.Encoding,
+fn count_edits(diffs: anytype, diff: anytype, edit_count: usize) void {
+ for (diffs.items) |diff| {
+ switch (diff.operation) {
+ .delete => edit_count += 1,
+ .equal => continue,
+ .insert => edit_count += 1,
+ }
+ }
+
+}
the analyzer rejects the result:
src/diff.zig:15:71: expected ',' after parameter
nothing was written; pass `apply: true` to make this edit
The remote ZLS instance analyzed the generated patch in memory and rejected it: the synthesized signature contained a parameter placement issue on line 15 (expected ',' after parameter).
This invocation omitted apply: true, so it requested only an in-memory preview. ZLS reported a syntax diagnostic for the proposed patch; the unchanged working tree reflects dry-run behavior and does not demonstrate that a disk write was blocked.
Testing the Zig Language Server with remote AST intelligence completes our four-flagship evaluation of Zig, illustrating how language servers, compilers, and financial engines uphold system invariants in modern systems programming.
Cite this article
Alexander Panasenko (2026-10-04). ZLS Under the Microscope: What 67 Remote AST Tools Found Inside the Zig Language Server. https://prod.codes/blog/zls-under-the-microscope-67-ast-tools/