A high-performance Language Server Protocol (LSP) implementation for WeChat Mini Program WXML files, built with Rust and Tree-sitter.
-
Smart Completions: Tag, attribute, and template name suggestions with full support for
usingComponents- Component tag completions with 70+ built-in WeChat components
- Attribute completions with type information and documentation
- Event binding completions (bind:, catch:, mut-bind:, etc.)
- Data binding completions inside
{{}}with TypeScript type inference - Template name completions across imported files
- Custom component completions from
usingComponents
-
Hover Information: Rich documentation at your fingertips
- Component documentation with attributes and events
- TypeScript type information for data bindings
- Property definitions for custom components
- Directive documentation (wx:if, wx:for, etc.)
-
Real-time Diagnostics: Catch errors as you type
- Undefined variable detection in mustache expressions
- Unknown component tag detection
- Missing required attributes
- Missing
wx:keyforwx:forloops - Invalid directive usage (wx:elif, wx:else validation)
- Event validation against component definitions
-
Code Navigation: Seamless WXML ↔ TypeScript integration
- Go to definition from WXML data bindings to TypeScript
- Go to definition for custom component properties
- Jump to custom component WXML files
- Find all references across WXML and TypeScript
- Support for nested loop variable resolution
-
Safe Refactoring: Rename variables with confidence
- Rename data bindings across WXML and TypeScript
- Prepare rename support for accurate range detection
-
Code Formatting: Consistent WXML formatting
- Automatic indentation and attribute formatting
- Preserves intentional line breaks
- Configurable indent size
| Feature | Status | Description |
|---|---|---|
| Completion | ✅ | Tags, attributes, template names, custom components, data bindings |
| Hover | ✅ | Component docs, type info, property definitions, directive docs |
| Diagnostics | ✅ | Undefined vars, unknown tags, missing attributes, wx:for validation |
| Go to Definition | ✅ | Data bindings → TS, Component tags → WXML, Attributes → TS properties |
| References | ✅ | Find variable usages across WXML and TS |
| Rename | ✅ | Safe variable renaming with prepare support |
| Document Formatting | ✅ | WXML code formatting |
| Document Sync | ✅ | Incremental synchronization |
curl -L https://github.com/puffinstudio/wechat-wxml-lsp/releases/latest/download/wxml-lsp-darwin-arm64 \
-o ~/.local/bin/wxml-lsp && chmod +x ~/.local/bin/wxml-lspcurl -L https://github.com/puffinstudio/wechat-wxml-lsp/releases/latest/download/wxml-lsp-darwin-x64 \
-o ~/.local/bin/wxml-lsp && chmod +x ~/.local/bin/wxml-lspcurl -L https://github.com/puffinstudio/wechat-wxml-lsp/releases/latest/download/wxml-lsp-linux-x64 \
-o ~/.local/bin/wxml-lsp && chmod +x ~/.local/bin/wxml-lspcurl -L https://github.com/puffinstudio/wechat-wxml-lsp/releases/latest/download/wxml-lsp-linux-arm64 \
-o ~/.local/bin/wxml-lsp && chmod +x ~/.local/bin/wxml-lspDownload wxml-lsp-win32-x64.exe from GitHub Releases and add to PATH.
bash <(curl -fsSL https://raw.githubusercontent.com/puffinstudio/wechat-wxml-lsp/main/scripts/install.sh)Install from VSCode Marketplace.
The extension automatically bundles the language server binary - no manual installation required.
Using lazy.nvim:
local wxml_lsp_cmd = { "wxml-lsp", "--stdio" }
local function add_ensure_installed(opts, lang)
opts.ensure_installed = opts.ensure_installed or {}
if type(opts.ensure_installed) == "table" and not vim.tbl_contains(opts.ensure_installed, lang) then
table.insert(opts.ensure_installed, lang)
end
end
local function start_wxml_lsp(args)
local bufname = vim.api.nvim_buf_get_name(args.buf)
if bufname == "" then
return
end
local root = vim.fs.root(bufname, { "project.config.json", ".git" }) or vim.fs.dirname(bufname)
if not root then
return
end
vim.lsp.start({
name = "wxml_lsp",
cmd = wxml_lsp_cmd,
root_dir = root,
})
end
return {
{
"nvim-treesitter/nvim-treesitter",
init = function()
vim.filetype.add({
extension = {
wxml = "wxml",
},
})
end,
opts = function(_, opts)
add_ensure_installed(opts, "wxml")
end,
},
{
"neovim/nvim-lspconfig",
init = function()
-- Register for both WXML and TypeScript files
-- TypeScript registration enables cross-file references (TS -> WXML)
vim.api.nvim_create_autocmd("FileType", {
pattern = { "wxml", "typescript" },
callback = start_wxml_lsp,
})
end,
},
}This configuration:
- Registers
.wxmlfiles as thewxmlfiletype - Installs the
wxmlTree-sitter grammar automatically (vianvim-treesitter) - Starts the language server for both
wxmlandtypescriptfiles - Enables cross-file references from TypeScript to WXML
- Auto-detects the project root using
project.config.jsonor.git
Mason is a Neovim package installer, not the language server itself. This server is not yet in the official Mason registry. The recommended approach is to install the binary manually (Step 1 above) and configure wxml_lsp_cmd to point to it. If wxml-lsp is on your $PATH, the above config works without any path changes.
The server analyzes your .ts files alongside .wxml to provide intelligent type information:
// index.ts
Page({
data: {
message: 'Hello',
items: [{ id: 1, name: 'Item' }],
user: {
profile: {
name: 'John'
}
}
}
})In index.wxml:
- Type
{{mes}}→ get completion formessage - Type
{{user.pro}}→ get completion forprofile - Hover over
{{message}}→ seemessage: stringwith markdown-formatted type information - Component hover uses markdown tables for clean attribute listings
- Press
gdon{{message}}→ jump to TS definition - Use
wx:for="{{items}}"→itemandindexare automatically typed
Custom components work the same way - define them in usingComponents and get full IDE support including property completions and type checking.
The language server also provides a CLI linting mode:
# Lint all WXML files in src/
wxml-lsp lint
# Lint with custom pattern
wxml-lsp lint "pages/**/*.wxml"
# Get detailed error output with colors
wxml-lsp lint src/pages/index.wxmlThis project is organized as a Rust workspace with the following crates:
server/- Main LSP server implementation
-
crates/wxml-parser/- Tree-sitter WXML parser- Custom Tree-sitter grammar for WXML
- Generates AST for syntax analysis
- Supports all WeChat Mini Program syntax including
wx:directives
-
crates/ts-analyzer/- TypeScript type extraction- Extracts type information from Page/Component data
- Resolves imports and exports
- Supports interface and type alias resolution
- Handles complex nested object types and arrays
crates/component-registry/- Built-in WeChat component definitions- 70+ official WeChat components with full metadata
- Attribute schemas with types and descriptions
- Event definitions for each component
- Auto-generated from official documentation
-
crates/wxml-diagnostics/- Diagnostic engine- Shared diagnostic computation for LSP and CLI
- Loop context resolution for wx:for variables
- Variable path type checking
- Template definition awareness
-
crates/wxml-linter/- CLI linting tool- Git-aware file discovery
- Batch linting with progress reporting
- Colored terminal output
-
editors/vscode/- VSCode extension- Automatic binary management
- File watching for live updates
- Integrated output channel
-
Language: Rust (Edition 2021)
-
LSP Framework: tower-lsp
-
Parser: Tree-sitter with custom WXML grammar
-
TypeScript Parser: tree-sitter-typescript
-
Async Runtime: Tokio
-
Concurrent Maps: DashMap
-
Text Rope: ropey
-
Serialization: serde
- Rust 1.75+ (stable)
- Node.js 20+ (for VSCode extension)
# Build all workspace crates
cargo build --workspace --release
# Run tests
cargo test --workspace
# Run specific test
cargo test --package component-registrycd editors/vscode
npm install
npm run compile
# Press F5 to launch Extension Development Hostwechat-wxml-lsp/
├── Cargo.toml # Workspace root
├── README.md # This file
├── scripts/
│ └── install.sh # One-line installer
├── server/
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs # CLI entry point
│ ├── lib.rs # Server library
│ ├── backend.rs # LSP handlers (2600+ lines)
│ └── document.rs # Document state management
├── crates/
│ ├── wxml-parser/ # Tree-sitter grammar
│ ├── ts-analyzer/ # TypeScript analysis (1400+ lines)
│ ├── component-registry/ # 70+ component definitions
│ ├── wxml-diagnostics/ # Diagnostic engine
│ └── wxml-linter/ # CLI tool
├── editors/
│ └── vscode/ # VSCode extension
├── tools/
│ └── fetch-wechat-components/ # Component scraper
└── .github/
└── workflows/ # CI/CD
- Continuous Integration: Tests run on every PR
- Binary Releases: Auto-built for all platforms on version tags (
v*)- Linux: x64, ARM64
- macOS: x64, ARM64 (Apple Silicon)
- Windows: x64
- VSCode Marketplace: Published automatically via GitHub Actions
Contributions are welcome! Please feel free to submit a Pull Request.
- Rust Code: Follow standard Rust formatting (
cargo fmt) - Tests: Add tests for new functionality
- Documentation: Update README.md for user-facing changes
- Component Data: Use
fetch-wechat-componentsto update component registry
When reporting issues, please include:
- Language server version (
wxml-lsp --version) - Editor/IDE and version
- Steps to reproduce
- Expected vs actual behavior
- Sample code (if applicable)
MIT
- tower-lsp - LSP framework for Rust
- tree-sitter - Parser generator
- WeChat Mini Program Documentation - Component definitions