ADR-019: Language Server as Separate Package
Context
Langium 4.x bundles parser and LSP in one module. The current packages/language/ package wires grammar and validation in src/module.ts, producing a WeltenwandererServices object that Langium uses both for parsing and for default LSP responses.
Custom LSP providers — hover documentation, completion item generation, code actions, formatting, rename, and semantic tokens — require domain-specific logic that goes substantially beyond validation. Hover over a require guard should explain the guard’s satisfiability verdict. Completion inside a decide clause must know which states and events the enclosing decider has already declared. These are analysis-level concerns, not grammar-level concerns.
ADR-001 chose Langium for both parsing and LSP integration, but it did not prescribe the package boundary between the compilation pipeline and the editor integration layer. At the time, no custom providers existed. That boundary is now a design decision.
Two options were considered:
| Option | Structure | Coupling concern |
|---|---|---|
Extend packages/language/ | All LSP providers added inside the existing package | Grammar + validation + editor integration in one package; grows without bound |
New packages/language-server/ | LSP providers in a dedicated package that depends on @weltenwanderer/language | Additional workspace package; clean compilation/editor boundary |
The compiler’s theoretical foundation reinforces the split. LSP providers for completion require Kan extension reasoning over the partial AST to propose valid completions. Hover providers require analysis results (guard satisfiability, exhaustiveness verdicts) that are produced by the analysis layer, not the grammar layer. Binding LSP providers to grammar compilation conflates two distinct transformation stages.
Decision
Create packages/language-server/ as a workspace package with the following properties:
- Declares
@weltenwanderer/languageas a dependency for AST types, grammar services, and validation results. - Overrides Langium’s default LSP provider implementations via the Langium DI module system, registering custom providers for hover, completion, code actions, formatting, rename, and semantic tokens.
- The package boundary follows a strict separation of concerns:
packages/language/owns the compilation pipeline (grammar, validation, analysis);packages/language-server/owns editor integration (all LSP providers and the server entry point). - The CLI’s future
lspsubcommand (Phase 5) delegates to this package rather than topackages/language/directly. - The VS Code extension (deferred,
packages/vscode/) wraps this package’s server entry point. - The Langium-generated
src/generated/files remain inpackages/language/. The language-server package imports from the generated types but does not regenerate them.
The DI override points that packages/language/src/module.ts must expose are documented in that package’s CLAUDE.md. These override points are treated as a stable internal API: breaking changes require a coordinated update across both packages.
Consequences
Positive
packages/language/stays focused on its single concern: the compilation pipeline. Its surface area does not grow as LSP features are added.- LSP providers can be developed, tested, and released independently of grammar or validation changes.
- The CLI and VS Code extension share one LSP implementation, eliminating the risk of divergence between the two editor integration surfaces.
- The package boundary maps cleanly onto the theoretical model: compilation pipeline (grammar → AST → validation → analysis) is separate from editor projection (analysis results → LSP responses).
- Langium’s default LSP providers remain functional as a fallback during development; custom providers can be added incrementally without disabling defaults globally.
Negative
- An additional workspace package increases monorepo complexity: one more
package.json, one moretsconfig.json, one more build step in the dependency graph. - The DI override interface between
packages/language/andpackages/language-server/must remain stable. A grammar refactor that changes service names or DI tokens requires coordinated changes in both packages. - Langium’s default LSP providers are functional for basic use. Custom providers must demonstrably improve the editing experience to justify their implementation cost. The bar for adding a custom provider is: measurable improvement over Langium’s default behaviour for
.dddfiles specifically.