A state-of-the-art Language Server Protocol (LSP) implementation designed for computational LiquidJS worksheets. It enforces static type safety, resolves nested schemas, offers smart auto-completions, formats syntax, and applies quick-fixes on-the-fly. Built to empower domain experts and developers writing complex calculation templates.
✨ Live Playground • 📚 Developer Reference • 🏗️ Architecture Overview
The monorepo separates the platform-agnostic core language intelligence from the environment runtimes (Node.js and Web Worker/Browser).
flowchart TD
%% Editor Layer
subgraph UI ["💻 Editor UI"]
VSC["VS Code Client"]
Monaco["Monaco Editor"]
end
%% Transport Layer
subgraph Transport ["🔌 Communication Bridges"]
Worker["Browser Web Worker"]
WS["WebSocket Server"]
Stdio["LSP Stdio Transport"]
end
%% Core Engine
subgraph Engine ["⚡ Core Engine"]
Common["lsp-common<br>(Type-System & LSP Handlers)"]
end
%% Connections
VSC <--> Stdio
Monaco <-->|"MessageChannel"| Worker
Monaco <-->|"WebSockets"| WS
WS <--> Stdio
Stdio <--> Common
Worker <--> Common
%% Styling
style UI fill:#faf5ff,stroke:#c084fc,stroke-width:1px
style Transport fill:#eff6ff,stroke:#60a5fa,stroke-width:1px
style Engine fill:#f0fdf4,stroke:#4ade80,stroke-width:2px
📁 liquid-lsp
├── 📁 packages
│ ├── 📄 key-pointer-schema # Schema parser and type-mapping registry
│ ├── 📄 liquid-core # Custom LiquidJS fork, tokenizer, Chevrotain tag parser
│ ├── 📄 lsp-common # Platform-agnostic core LSP handlers & TypeSystem
│ ├── 📄 lsp-node # Node.js stdio/socket server runtime
│ └── 📄 lsp-browser # Web Worker compilation & Monaco client integration
├── 📁 vscode-extension # VS Code client extension and configuration
├── 📁 express-server # Express playground hosting Monaco Editor
├── 📁 angular-playground # Angular-based Monaco playground client
└── 📁 lsp-engine # Workspace integration testing suite
| Feature | LSP Method / Diagnostic | Quick Fix |
|---|---|---|
| 🔍 Static Analysis & Diagnostics | ||
| Type Inference | Diagnostics (assign, assignVar, parseAssign, computeColumn, loops) |
— |
| Composite Property Validation | Diagnostics (dot-path schema matching) | — |
| Loop Variable Type Narrowing | Diagnostics (collection item typing) | — |
| Type Mismatch Linting | Diagnostics (filter constraints) | — |
| Nil / Optional Safety | Diagnostics (optional properties accessed without fallback) | ✅ Yes |
| Unused Variable Warnings | Diagnostics (dead-assign detection) | — |
| Multi-Branch Type Consistency | Diagnostics (assert types match across if/else) |
✅ Yes |
| Filter Parameter Validation | Diagnostics (arg types, division-by-zero, placeholders) | — |
| Engine & Tag Validations | Diagnostics (computeColumn rules, invalid JSON structures) |
— |
| Syntax Errors Reporting | Diagnostics (token-by-token concurrent Chevrotain errors) | — |
| ⚡ Smart Code Actions & Quick Fixes | ||
| Inline Math Converter | textDocument/codeAction (+ to | plus:) |
✅ Yes |
| Single-Equals Correction | textDocument/codeAction (= to == inside conditional) |
✅ Yes |
| Filter Spelling Correction | textDocument/codeAction (Levenshtein match suggestions) |
✅ Yes |
| Quoted Filter Name Fix | textDocument/codeAction (| "upcase" to | upcase) |
✅ Yes |
| Unclosed Tag Auto-Insertion | textDocument/codeAction (appends end tags) |
✅ Yes |
| 💡 Editor Intelligence | ||
| Smart Autocomplete | textDocument/completion (variables, filters, tags, dot-paths) |
— |
| Rich Hover Cards | textDocument/hover (type hierarchy, docs, options) |
— |
| Schema-Aware Hover Docs | textDocument/hover (dynamic contextual examples) |
— |
| Filter Signature Help | textDocument/signatureHelp (parameter lists & documentation) |
— |
| Go-to-Definition | textDocument/definition (declaration locations, JSON keys) |
— |
| Document Outline | textDocument/documentSymbol (symbols hierarchy tree) |
— |
| Semantic Flow Highlighting | textDocument/semanticTokens (color-codes variable roles) |
— |
| Rename Schema Guards | textDocument/rename (API schema protection & local shadowing) |
— |
Statically resolves types across standard assignments, custom JSON structures, and collections:
{% assign price = 100 %} {# → number #}
{% assign label = "Invoice" %} {# → string #}
{% parseAssign item = '{"title": "Seat", "cost": 450}' %}
{# → composite: { title: string, cost: number } #}
{% for row in item_list %}
{{ row.title }} {# ✅ row typed from item_list element composite #}
{{ row.non_existent }} {# ⚠️ Property "non_existent" does not exist on row { title: string, cost: number } #}
{% endfor %}Enforces schema compliance for deeply-nested dot-notation accesses:
{{ user.address.zipcode }} {# ✅ resolved through composite nesting #}
{{ user.phone.fax }} {# ⚠️ Property "fax" does not exist on "phone" { number: string, code: string } #}Warns when filter requirements conflict with the incoming data type.
{% assign name = "john" %}
{% assign result = name | plus: 25 %}Warning
LSP Diagnostic: Type mismatch: Math filter "plus" is applied to a string value.
Flags variables overwritten before they are read, saving computational and rendering cycles:
{% assign score = 100 %}
{% assign score = 200 %} {# ⚠️ "score" was overwritten before its value was ever read #}Translates standard infix mathematical notation into Liquid's pipeline format.
{% assign total = price + 5 %}Tip
Quick Fix: Convert to {% assign total = price | plus: 5 %}
Prevents accidental assignments inside conditionals.
{% if status = "Active" %}Important
LSP Diagnostic: Assignments are not allowed inside conditional statements.
Quick Fix: Convert to {% if status == "Active" %}
Normalizes whitespace, formatting tags, quote marks, and block indentation.
Before:
{% if status == 'Active' %}
{{name|upcase}}
{% else %}
{{price}}
{% endif %}After (Formatted):
{% if status == "Active" %}
{{ name | upcase }}
{% else %}
{{ price }}
{% endif %}Note
Enforces standard rules: 2-space indentation, quote normalization (' → "), uniform delimiter spacing, and consecutive tag splitting.
Reveals nested type documentation and field lists on hover:
user.address
─────────────────────────
composite {
street: string
city_name: string
pincode: string
}Shows signature helpers when typing filter parameters:
{{ description | truncate: [length: number, truncate_string: string = "..."] }}
Uses Levenshtein distance to offer quick fixes for mistyped filters:
{{ "hello" | upcasee }}Tip
Quick Fix: Unknown filter "upcasee". Did you mean "upcase"?
The server can be deployed in four flexible topologies:
Spawns the Node.js language server binary directly as a subprocess of the editor client. No manual setup required.
Runs the LSP server on a remote server for environments running thin clients.
# On remote host:
node lsp-engine/dist/main.js --socket=6009Configure your client (e.g. VS Code settings.json):
"liquid.server.mode": "remote",
"liquid.server.host": "your-remote-server-ip",
"liquid.server.port": 6009No backend server required. Runs completely client-side in the browser by compiling into a Web Worker:
import { connectBrowserLspWorker } from '/lsp-browser-client.js';
const client = await connectBrowserLspWorker('/lsp-worker.js');
await client.sendRequest('initialize', {
capabilities: {},
initializationOptions: {
schema: {
/* client schema here */
},
},
});Wires a remote Monaco Editor client to the LSP server over WebSockets:
pnpm run start:playground # Starts playground at http://localhost:3000Includes a live-reloading interactive code editor with real-time linting, formatting, hover tips, autocomplete, and theme toggling.
- Node.js:
v20.xor higher - pnpm:
v10.xor higher (npm install -g pnpm)
Get the workspace up and running locally:
pnpm install
pnpm run buildRun commands from the repository root:
pnpm test # Run vitest suite across all packages
pnpm run start:playground # Launch local Monaco Playground (http://localhost:3000)
pnpm run lint # Run ESLint validation
pnpm run format # Re-format files with Prettier
pnpm run package:extension # Compile & package VS Code extension (.vsix)- Open the repository root directory in VS Code.
- Open Run and Debug (
Ctrl+Shift+DorCmd+Shift+D). - Select
Debug Client & Serverand pressF5. - Set breakpoints in
vscode-extension/src/client.tsorpackages/lsp-common/src/**.
- TypeScript 6.x: High-performance, modern type-safety with a strict no-
anypolicy. - ES Modules: Standard Node ESM (
type: module) using explicit.jsimport extensions. - pnpm Workspaces: Clean monorepo dependency orchestration and local linking.
- Chevrotain Parser: High-performance custom parser for token-by-token analysis and precise error ranges.
- LiquidJS Fork: Uses
github:sonuKumar03/liquidjswhich contributes tag parsers likecomputeColumn,assignVar, andparseAssign. - LSP Protocol: Fully compatible with LSP v3.17 (
vscode-languageserver). - Testing: Unified Vitest suite executing unit tests and full JSON-RPC integration test scenarios.
To learn more about specific components and internals, explore these documents:
| Document | Purpose / Highlights |
|---|---|
| AGENTS.md | Monorepo architecture roadmap, coding style rules, and AI development guide. |
| developer_reference.md | Step-by-step instructions for adding features (Diagnostics, Quick Fixes, Autocomplete, Hover). |
| handover.md | Handover blueprint containing design rationale, codebase state, and future roadmap. |
| key-pointer-schema README | Parser and mapper specifications for variable wire-format schemas. |
| liquid-core README | Custom LiquidJS parser, Chevrotain grammar, tokenizer, and tags. |
| lsp-common README | Platform-agnostic LSP implementation handlers, state, and type system. |