This guide covers building, testing, and contributing to GraphQL Analyzer.
- Rust toolchain (see
rust-toolchain.tomlfor version) - Node.js and pnpm (for VS Code extension; versions pinned via
mise— runmise install) - Cargo (included with Rust)
cargo build --workspace# CLI
cargo build --package graphql-cli
# LSP server
cargo build --package graphql-lsp
# Release builds
cargo build --workspace --releasecargo install --git https://github.com/trevor-scheer/graphql-analyzer graphql-cliThis project uses cargo-nextest for running tests. It runs
test binaries in parallel and provides better output than cargo test.
# Install nextest
cargo install cargo-nextest
# Run all tests
cargo nextest run --workspace
# Run tests for a specific crate
cargo nextest run --package graphql-linter
# Run a specific test by name
cargo nextest run --workspace test_name
# Run with output (stdout visible)
cargo nextest run --workspace --no-captureNote: nextest doesn't support doctests. Run
cargo test --workspace --docseparately if needed.
# Format code
cargo fmt
# Lint with Clippy
cargo clippy --workspaceThe repo is a single pnpm workspace root. All JavaScript/TypeScript packages
are workspaces referenced from the root package.json:
| Workspace | Contents |
|---|---|
editors/vscode |
VS Code extension |
packages/core |
@graphql-analyzer/core dispatcher (napi-rs) |
packages/core/npm/<triple> |
Per-platform native addon stubs (5 packages) |
packages/eslint-plugin |
@graphql-analyzer/eslint-plugin |
test-workspace/<project> |
Fixture projects for LSP/CLI tests |
One pnpm install at the repo root wires everything together — workspace deps
resolve via symlinks rather than going through the registry.
| Script | Runs |
|---|---|
pnpm run build |
build in every workspace that defines it |
pnpm run build:debug |
build:debug in every workspace that defines it |
pnpm run compile |
compile in every workspace that defines it (VS Code) |
pnpm run watch |
watch in every workspace that defines it (VS Code) |
pnpm run typecheck |
tsc -b across the TypeScript project graph |
pnpm run lint |
oxlint . |
pnpm run package |
Package the VS Code extension |
pnpm run test:unit |
test:unit in every workspace that defines it |
pnpm run test:e2e |
test:e2e in every workspace that defines it |
pnpm run fmt |
oxfmt --write . |
pnpm run fmt:check |
oxfmt --check . (CI-friendly; no writes) |
cd editors/vscode
pnpm install
pnpm run compile- Open
editors/vscodein VS Code - Press
F5to launch the Extension Development Host - The extension automatically uses
target/debug/graphql-lspwhen running from the repo
pnpm run compile # Build extension
pnpm run watch # Watch mode
pnpm run format # Format TypeScript
pnpm run lint # Lint TypeScript
pnpm run package # Create .vsix fileTo test a platform-specific extension build from a PR, comment /build-extension on the PR. This will:
- Build LSP binaries for all platforms
- Package platform-specific VSIXs
- Post a comment with download links
The ESLint plugin (@graphql-analyzer/eslint-plugin) is a thin TypeScript
layer on top of @graphql-analyzer/core, which is the Rust analyzer compiled
to a native Node addon via napi-rs.
# Build the native addon (debug — fast rebuilds; use `build` for release)
pnpm --filter @graphql-analyzer/core run build:debug
# Build the ESLint plugin TS sources
pnpm --filter @graphql-analyzer/eslint-plugin run buildThe debug build produces packages/core/graphql-analyzer.<triple>.node; the
platform stubs under packages/core/npm/<triple>/ pick up the .node file
from there.
The test-workspace/eslint-migration project is a demo workspace configured
to run both @graphql-eslint/eslint-plugin and @graphql-analyzer/eslint-plugin
against the same fixtures for comparison.
# Run graphql-analyzer plugin
pnpm --filter eslint-migration run lint:after
# Run graphql-eslint for comparison
pnpm --filter eslint-migration run lint:before# napi-rs doesn't have a watch mode; install cargo-watch (`cargo install cargo-watch`)
# if you want auto-rebuild on Rust source changes.
cargo watch -p graphql-analyzer-napi -s 'pnpm --filter @graphql-analyzer/core run build:debug'
# Plugin TS watch
pnpm --filter @graphql-analyzer/eslint-plugin run devThe packages/web-ide package hosts a Monaco-based browser playground wired to a wasm build of the language server. Useful for demos, prototyping, and validating cross-target behavior without spinning up VS Code.
rustup target add wasm32-unknown-unknown
cargo install wasm-packcargo xtask web --dev # opens http://localhost:5173 with hot reload
cargo xtask web # static build under packages/web-ide/dist/xtask web runs wasm-pack build for crates/lsp-wasm and then either pnpm run dev (when --dev) or pnpm run build against packages/web-ide. Vite serves the worker, the wasm bundle, and Monaco from a single dev server.
The playground has a small Playwright suite at packages/web-ide/tests/web-ide.spec.ts:
cd packages/web-ide
pnpm exec playwright install chromium
pnpm run test:e2eThe tests run against the same Vite dev server as xtask web --dev.
# Run all benchmarks
cargo bench
# Run specific benchmark
cargo bench parse_cold
# Save baseline for comparison
cargo bench -- --save-baseline main
# Compare against baseline
cargo bench -- --baseline main# Run with debug logging
RUST_LOG=debug target/debug/graphql-lsp
# Module-specific logging
RUST_LOG=graphql_lsp=debug,graphql_analysis=info target/debug/graphql-lspgraphql-analyzer supports OpenTelemetry tracing for diagnosing performance issues. Traces provide detailed timing data for LSP operations like file changes, validation, and schema loading.
- Start a collector (see Running Jaeger below)
- Enable OTEL in VS Code settings:
- Set
graphql-analyzer.debug.otelEnabledtotrue - Optionally adjust
graphql-analyzer.debug.otelEndpoint(default:http://localhost:4317)
- Set
- Restart the language server (Command Palette: "graphql-analyzer: Restart Language Server")
- Use the "graphql-analyzer: Test OpenTelemetry Connection" command to verify connectivity
- Open http://localhost:16686 to view traces
| Setting | Type | Default | Description |
|---|---|---|---|
debug.logLevel |
string | warn |
Server log verbosity. Higher levels may impact performance on large codebases. |
debug.otelEnabled |
boolean | false |
Export traces via OpenTelemetry to an OTLP collector. |
debug.otelEndpoint |
string | http://localhost:4317 |
OTLP collector gRPC endpoint. |
All settings are under the graphql-analyzer namespace.
# Run with tracing enabled
OTEL_TRACES_ENABLED=1 target/debug/graphql-lsp
# Custom endpoint
OTEL_TRACES_ENABLED=1 \
OTEL_EXPORTER_OTLP_ENDPOINT=http://my-collector:4317 \
target/debug/graphql-lsp
# Combine with log level control
RUST_LOG=info OTEL_TRACES_ENABLED=1 target/debug/graphql-lspJaeger is an open-source tracing backend that works out of the box with graphql-analyzer's OTLP export.
Docker:
docker run -d --name jaeger \
-p 4317:4317 \
-p 16686:16686 \
jaegertracing/all-in-one:latestPodman:
podman run -d --name jaeger \
-p 4317:4317 \
-p 16686:16686 \
docker.io/jaegertracing/all-in-one:latestDocker Compose:
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "4317:4317" # OTLP gRPC
- "16686:16686" # Jaeger UI| Port | Purpose |
|---|---|
| 4317 | OTLP gRPC ingestion (what graphql-analyzer connects to) |
| 16686 | Jaeger UI for viewing traces |
To stop Jaeger: docker stop jaeger && docker rm jaeger (or podman).
The default log level is warn, which has negligible overhead even on large
codebases (10k+ files). Setting the level to info or debug activates text
log formatting for all instrumented functions, which can noticeably impact
performance during initial load and file changes.
For performance investigation, use OTEL tracing (which batches and exports asynchronously) rather than increasing the log level. To debug a specific module without global overhead:
RUST_LOG=warn,graphql_lsp::server=debug target/debug/graphql-lspIn Jaeger UI, look for the graphql-analyzer service. Key spans:
did_change-- Triggered on every edit. Shows time in change application and re-validation.did_save-- Triggered on file save. Includes cross-file diagnostics.validate_file_with_snapshot-- Core validation path (parsing, HIR, analysis).load_workspace_config-- Initial project loading (config, file discovery, introspection).
- No traces appearing: Run "graphql-analyzer: Test OpenTelemetry Connection" to verify the collector is reachable. Check the "graphql-analyzer Debug" output channel for OTEL messages. Ensure the server was restarted after enabling OTEL.
- Collector not reachable: Verify the container is running (
docker ps). Check port 4317 is free. - High overhead: Check if
debug.logLevelisinfoordebug-- text log formatting is typically the bottleneck, not OTEL.
graphql-analyzer/
├── crates/
│ ├── analysis/ # Validation layer (Salsa queries)
│ ├── base-db/ # Salsa database foundation
│ ├── cli/ # CLI tool
│ ├── config/ # .graphqlrc parser
│ ├── extract/ # Extract GraphQL from TS/JS
│ ├── hir/ # High-level IR (semantic layer)
│ ├── ide/ # IDE features API
│ ├── ide-db/ # IDE database extensions
│ ├── introspect/ # Remote schema introspection
│ ├── linter/ # Lint rules engine
│ ├── lsp/ # LSP server
│ ├── mcp/ # MCP server
│ ├── napi/ # napi-rs native addon bindings
│ └── syntax/ # Parsing layer
├── editors/
│ └── vscode/ # VS Code extension
├── packages/
│ ├── core/ # @graphql-analyzer/core (dispatcher + platform stubs)
│ └── eslint-plugin/# @graphql-analyzer/eslint-plugin
├── benches/ # Performance benchmarks
└── tests/ # Integration tests
The codebase uses a query-based, incremental architecture inspired by rust-analyzer:
graphql-lsp / graphql-cli / graphql-mcp
↓
graphql-ide (Editor API)
↓
graphql-analysis (Validation + Linting)
↓
graphql-hir (Semantic layer)
↓
graphql-syntax (Parsing)
↓
graphql-db (Salsa database)
Key technologies:
- Salsa - Incremental computation framework
- lsp-server + crossbeam-channel + threadpool - sync LSP main loop and worker pool (rust-analyzer-style)
- apollo-compiler - GraphQL parsing and validation
Releases are automated via CI using Knope with changesets.
# Interactive mode
knope document-change
# Or create manually in .changeset/Changeset format:
---
graphql-analyzer-lsp: minor
---
Add support for feature XTarget one or more knope package names:
| Change | Target |
|---|---|
| CLI feature or bug fix | graphql-analyzer-cli |
| LSP or VS Code extension change | graphql-analyzer-lsp (coupled) |
| MCP server change | graphql-analyzer-mcp |
| Native addon (Rust or any npm package) | graphql-analyzer-core |
| ESLint plugin (JS-only change) | graphql-analyzer-eslint-plugin |
See RELEASES.md for the full release pipeline.
- Create changesets for your changes
- Merge to main
- CI creates a Release PR
- Merge the Release PR
- CI builds and publishes releases
- Fork the repository
- Create a feature branch
- Make your changes
- Run
cargo fmtandcargo clippy - Run
cargo test - Open a pull request
For VS Code extension changes, also run:
cd editors/vscode
pnpm run format:check
pnpm run lint