tooling
LSP EXTENSION LLD
Editor integration, implementation workflow, and operational runbooks.
Context Rail
Tags and Themes
On This Page
- 1. Architecture Overview
- 2. Runtime Components
- 2.1 LSP binary entrypoint
- 2.2 JSON-RPC transport bridge
- 2.3 Server state and handler wiring
- 2.4 Document store
- 2.5 Analyzer snapshot model
- 2.6 Workspace symbol index
- 3. LSP Capability Map (Method -> Implementation)
- 4. Key Request Flows
- 4.1 Open/change diagnostics flow
- 4.2 Completion flow
- 4.3 Rename flow
- 5. Diagnostics and Linting
- 6. Formatting Design
- 7. Semantic Tokens and Resilience
- 8. Hover, Signature Help, and Teaching Metadata
- 9. VS Code Extension Design
- 9.1 Binary selection algorithm
- 9.2 Crash-loop mitigation
- 9.3 Commands exposed
- 10. Deterministic Behavior Rules
- 11. Testing Strategy
- 11.1 Server tests
- 11.2 Analyzer tests
- 11.3 Extension integration tests
- 12. Change Playbooks
- 12.1 Add a new LSP feature
- 12.2 Add a new builtin function
- 12.3 Add a new lint rule
- 12.4 Add or change extension setting
- 13. Build, Run, and Debug
- 14. Known Design Trade-offs
- 15. Related Docs
Mutant LSP + VS Code Extension Low-Level Design (LLD)
This is the implementation-level developer guide for the Mutant language tooling stack.
Audience:
- Junior or new engineers who need to understand how Mutant language tooling works end-to-end.
- Maintainers adding or changing LSP features, diagnostics, formatting, completion, hover/signature behavior, or extension lifecycle behavior.
Scope:
- Go LSP server in lsp/cmd/mlsp/main.go and lsp/internal/server/server.go
- Analyzer pipeline in lsp/internal/analyzer
- Workspace state/index in lsp/internal/workspace
- VS Code extension host/client in mutant-vscode-extension/src/extension.ts
1. Architecture Overview
Core separation of concerns:
- Extension is process lifecycle + UX shell around the language server.
- LSP server is request handling, state, and protocol behavior.
- Analyzer is language intelligence over current AST snapshot.
- Workspace index enables cross-file symbol/reference behavior.
2. Runtime Components
2.1 LSP binary entrypoint
- Entrypoint: lsp/cmd/mlsp/main.go
- Behavior:
- Parse
-debugflag - Build server via
server.New(debug) - Run over stdio via
server.Run()
2.2 JSON-RPC transport bridge
- Adapter: lsp/internal/server/transport.go
- Why it exists:
glsphandler methods are wired into ajsonrpc2stdio connection.- It translates method/params and maps validation failures to JSON-RPC error codes.
Transport flow:
- Read request from stdin stream.
- Build
glsp.Contextwith method, params, Notify, and Call handlers. - Dispatch with
handler.Handle(...). - Reply (unless notification).
2.3 Server state and handler wiring
- Main implementation: lsp/internal/server/server.go
Server owns:
- LSP method handler table
- Document store (
documents) - Symbol index (
symbols) - Analyzer instance (
analyzer) - Per-document snapshots map (
snapshots) - Lint configuration
- Crash/fallback flags (for semantic token panic fallback warning)
Important constructor behavior:
New(debug bool)binds all supported LSP endpoints to receiver methods.initializeadvertises capabilities like completion, hover, signature help, references, rename, formatting, semantic tokens.
2.4 Document store
- Files: lsp/internal/workspace/store.go, lsp/internal/workspace/document.go
- Responsibility:
- Track opened documents and versions.
- Apply incremental text changes from LSP content change events.
- Return immutable clone snapshots to avoid accidental shared mutation.
2.5 Analyzer snapshot model
- Files: lsp/internal/analyzer/snapshot.go, lsp/internal/analyzer/analyzer.go
- Snapshot contains:
- raw source
- parsed AST program (
Program) - parse errors (hard failures)
- recoverables (
Recoverables): non-fatal parser findings — most importantly the missing/redundant semicolon channel — surfaced viaSemicolonProblems(). The tree still parses into a usable AST; these drive thesemicolondiagnostic and its quick fixes.
Analyzer steps:
- Lex + parse source into AST.
- Preserve node ranges (
NodePositions) from parser output. - Serve semantic queries (hover, completion, definitions, references, symbols, semantic tokens, signature help).
2.6 Workspace symbol index
- File: lsp/internal/workspace/symbol_index.go
- Purpose:
- Cache top-level symbols per document for workspace symbol search.
- Cache unresolved identifier usages to improve cross-document references.
This enables cross-file behaviors when a symbol is not resolvable only within a single snapshot.
3. LSP Capability Map (Method -> Implementation)
| LSP method | Server method | Core implementation dependencies |
|---|---|---|
| initialize | initialize |
Capabilities + semantic legend from analyzer |
| textDocument/didOpen | didOpen |
store open -> analyze -> set snapshot -> publish diagnostics |
| textDocument/didChange | didChange |
incremental apply -> analyze -> publish diagnostics |
| textDocument/didClose | didClose |
delete store/snapshot/index + clear diagnostics |
| textDocument/hover | hover |
Snapshot.HoverText |
| textDocument/completion | completion |
Snapshot.CompletionItemsAt |
| textDocument/signatureHelp | signatureHelp |
Snapshot.SignatureHelp |
| textDocument/documentSymbol | documentSymbols |
Snapshot.DocumentSymbols |
| textDocument/definition | definition |
local definition + workspace fallback |
| textDocument/typeDefinition | typeDefinition |
Snapshot.TypeDefinitionLocation |
| textDocument/references | references |
local refs + workspace refs + dedupe |
| textDocument/prepareRename | prepareRename |
local rename target + workspace fallback |
| textDocument/rename | rename |
location collection + per-URI sorted text edits |
| textDocument/codeAction | codeActions |
diagnostic-driven quick fixes |
| textDocument/semanticTokens/full | semanticTokensFull |
semantic token data + panic fallback |
| textDocument/formatting | formatting |
AST formatter + safety-preserving fallback |
| textDocument/onTypeFormatting | onTypeFormatting |
trigger-based canonicalization while typing (opt-in) |
| workspace/symbol | workspaceSymbols |
SymbolIndex query |
| workspace/didChangeConfiguration | didChangeConfiguration |
lint config parse + diagnostics republish |
4. Key Request Flows
4.1 Open/change diagnostics flow
4.2 Completion flow
Implementation notes:
- Completions include keywords, builtins, snippets, visible scope bindings.
- Ordering is deterministic and stabilized with explicit sort keys in analyzer.
4.3 Rename flow
5. Diagnostics and Linting
Core file: lsp/internal/analyzer/diagnostics.go
Diagnostics sources:
mutant-parser: parser errors from snapshot parse errors, plus the string/comment-aware delimiter-balance checker.mutant-lint: semantic lint rules.mutant-format: strict-formatting rules (the semicolon rule) — distinct frommutant-lintso quick fixes can key off it.
Current lint rules (rule id -> default severity):
duplicateTopLevelDeclaration-> warning (also nested duplicates)unusedDeclaration-> warning (top-level and local; skips_)undefinedDeclaration-> error (scope-aware; builtins + macro special forms count as defined)nestingComplexity-> warning (if/for nesting depth > 2 in function bodies)semicolon-> warning (missing/redundant;, sourcemutant-format; both have quick fixes and the formatter also repairs them on save)unreachableCode-> warning (statements after an unconditionalreturn/break/continuein a statement list; literal control flow only)platformSupport-> warning (OS-aware: warns when a program calls a builtin that is not supported on the operating system the language server is running on, e.g. a Windows/Linux-only builtin such asprocess_modulesused on macOS). The supported-platform set comes frombuiltin.PlatformSupport/builtin.UnsupportedOnin builtin/metadata.go; the host OS isruntime.GOOS(overridable in tests via the analyzer'shostGOOS).
Config ingestion path:
- Extension sends settings changes via
workspace/didChangeConfiguration. - Server parses with lsp/internal/server/lint_config.go.
republishAllDiagnosticsupdates existing open documents.
Supported severities:
- error, warning, information, hint, off
6. Formatting Design
Core file: lsp/internal/server/formatter.go
Behavior model:
- If hard parse errors exist (or the snapshot is invalid), the formatter degrades to whitespace normalization only (CRLF -> LF, strip trailing whitespace, exactly one trailing newline).
- Otherwise it applies AST-driven formatting for statements/expressions. Comments
and blank lines are handled through the AST printer (re-attached from the
program.Commentsside-table; runs of blank lines collapse to one), so the presence of comments/blank lines no longer forces the normalization path.
Strict semicolons (canonical):
- Semicolons are emitted from the AST via
ast.Statement.RequiresSemicolon(), not copied from source. The formatter therefore repairs missing semicolons and removes redundant ones on format. Recoverable semicolon issues do not trigger the normalization fallback; only hard parse errors do. Struct fields are;-terminated including the last.
Canonical style policy:
- Four-space indent (never tabs); client
tabSize/insertSpacesare ignored. The only user control is the mastermutant.strictFormattingon/off toggle. - Opening braces stay on the same line for supported constructs (
if (...) {,for (...) {,fn(...) {,else {); operator expressions are fully parenthesized and space-padded for a canonical form.
On-type formatting behavior:
- Server advertises
textDocument/onTypeFormattingand supports triggers for},;, and newline. - Extension keeps on-type formatting disabled by default to avoid intrusive edits, and exposes an opt-in setting.
Important safety guarantees:
- String literal quotes are preserved and escaped.
- Formatting returns nil edits for noop output.
7. Semantic Tokens and Resilience
Semantic token production is in analyzer logic and requested through
textDocument/semanticTokens/full.
Resilience behavior in lsp/internal/server/server.go:
- method-level panic recovery in semantic token handler
- one-time user warning if fallback path is used
- returns empty token list on failure instead of crashing LSP process
This prevents editor crash loops from semantic token panics.
8. Hover, Signature Help, and Teaching Metadata
Teaching metadata source:
What it provides:
- keyword hover docs
- builtin hover/signature docs
- snippet completion templates
Builtin coverage model:
- Rich docs from
builtinDocsmap when defined. - Prefix-based fallback for builtin families (
fs_,db_,bytes_,http_, etc.). - Generic fallback for any builtin registered in builtin/builtin.go.
Capability categories and platform metadata (both surfaced without a custom token legend):
- Hover appends a
_Category: <capability>_line (frombuiltin.CapabilityCategory, e.g.filesystem,network,graph database,runtime integration,forensics) and, for platform-constrained builtins, a Platforms: line plus any behavioral note (frombuiltin.PlatformSupport). - Completion
Detailbecomesbuiltin · <capability>so the completion list shows the category inline. Sorting still groups all builtins together (the completion category test now prefix-matchesbuiltin).
Result:
- Newly added builtins are auto-discoverable in completion and still have baseline hover/signature coverage, with their capability category and platform support shown automatically.
9. VS Code Extension Design
Main file: mutant-vscode-extension/src/extension.ts
Responsibilities:
- Activate language client and commands.
- Resolve language server command path.
- Manage restarts and crash-loop backoff.
- Offer operational commands (status, logs, restart, smoke checks).
9.1 Binary selection algorithm
If mutant.languageServer.path is configured and non-empty:
- use it exactly.
Else:
- Gather workspace roots and parent directories.
- Search for
mlsp*binaries with accepted naming variants. - Pick latest by modification time.
- Fallback to
mlsp.exeon Windows ormlspelsewhere.
Code path:
resolveLanguageServerCommandFromInputsfindLatestServerBinaryisLanguageServerBinaryName
9.2 Crash-loop mitigation
The extension tracks crash timestamps in a rolling window:
- window: 3 minutes
- block threshold: 5 crashes in window
When threshold is reached:
- automatic restart is disabled
- user is prompted to run manual restart command
Code path:
recordCrashAndGetStatus- language client
errorHandler.closed
9.3 Commands exposed
Declared in mutant-vscode-extension/package.json, implemented in mutant-vscode-extension/src/extension.ts:
- Mutant: Open Smoke File
- Mutant: Run LSP Smoke Checks
- Mutant: Show LSP Status
- Mutant: Show LSP Logs
- Mutant: Restart LSP
- Mutant: Copy LSP Logs
10. Deterministic Behavior Rules
Determinism safeguards already in codebase:
- Completion ordering is canonicalized and stable (category + label + kind +
explicit
SortText). - Node selection tie-breaks use deterministic specificity logic.
- Workspace symbol ordering is sorted by name/location.
- Rename edits are sorted by range per file before response.
These rules reduce editor flicker and test flakiness.
11. Testing Strategy
11.1 Server tests
- File: lsp/internal/server/server_test.go
- Covers initialization capabilities, diagnostics, completion, hover, references, formatting, semantic tokens, rename, configuration behavior, and regressions.
11.2 Analyzer tests
- File: lsp/internal/analyzer/analyzer_test.go
- Includes semantic robustness and builtin teaching coverage regression checks.
11.3 Extension integration tests
- File: mutant-vscode-extension/src/test/suite/extension.test.ts
- Covers activation, command registration, crash backoff, binary selection, and config override behavior.
12. Change Playbooks
12.1 Add a new LSP feature
- Add capability advertisement in
initializeif needed. - Add server handler in constructor wiring.
- Implement method in server layer.
- Add analyzer APIs if semantic analysis is needed.
- Add tests in server/analyzer test suites.
- Add extension-side UX command only if user-facing operation is needed.
12.2 Add a new builtin function
- Register builtin in builtin/builtin.go (append-only;
see the 4 touch-points:
names.goconst,builtin.goslice, impl func,metadata.godoc). - Add a rich doc entry to the
builtinDocsmap in builtin/metadata.go (required — a meta-test enforces per-function docs). The LSP reads hover/signature/completion docs from here. - If the builtin is platform-constrained, set
platforms(supported GOOS set) and/orplatformNoteon itsbuiltinDoc; theplatformSupportdiagnostic and hover pick it up automatically. Its capability category is derived from the name prefix inCapabilityCategory(extendcapabilityCategoriesif it is a new family). - Run analyzer/server tests. Existing regression tests verify baseline completion/hover/signature coverage for all builtins.
12.3 Add a new lint rule
- Implement lint function in lsp/internal/analyzer/diagnostics.go.
- Add config field to
LintConfigand severity parser in lsp/internal/server/lint_config.go. - Expose setting in mutant-vscode-extension/package.json.
- Add tests for default severity, override, and off behavior.
12.4 Add or change extension setting
- Add schema entry under
contributes.configuration.propertiesin mutant-vscode-extension/package.json. - Read/consume in mutant-vscode-extension/src/extension.ts.
- Add integration test in mutant-vscode-extension/src/test/suite/extension.test.ts.
- Document in mutant-vscode-extension/README.md and troubleshooting docs.
13. Build, Run, and Debug
LSP/server tests:
go test ./lsp/internal/server -vgo test ./lsp/internal/analyzer -vgo test ./...
Extension:
cd vscode-extensionnpm installnpm run compilenpm test- Launch Extension Development Host with VS Code
F5
Operational debugging:
- Use
Mutant: Show LSP Status - Use
Mutant: Show LSP Logs - Use
Mutant: Copy LSP Logs - Use
Mutant: Restart LSPfor manual recovery
14. Known Design Trade-offs
- Current formatter chooses preservation for comments/blank-line input rather than forcing full AST rewrite, to avoid destructive edits.
- Workspace reference fallback focuses on top-level symbols for predictable and performant cross-document behavior.
- Semantic token failures degrade gracefully to empty token data instead of hard-failing language services.
15. Related Docs
- docs/LSP_EXTENSION_ONBOARDING_60_MIN.md
- docs/MUTANT_LANGUAGE_REFERENCE.md
- docs/CAPABILITY_REFERENCE.md