pi-lens supports 37+ languages through dispatch runners and LSP integration.
Formatting uses a single selected formatter per file: explicit project config wins, otherwise pi-lens uses a smart default where supported, and config-first ecosystems do not autoformat without config.
Dispatch is diagnostics-oriented: automatic formatting and safe autofix happen in the post-write pipeline rather than through dispatch format-check runners.
| Language | LSP | Dispatch Runners | Formatter |
|---|---|---|---|
| JavaScript/TypeScript | ✓ | lsp, ts-lsp, biome-check-json, tree-sitter, ast-grep-napi, type-safety, similarity, fact-rules, eslint, oxlint | biome, prettier |
| Python | ✓ | lsp, pyright, mypy (config-first), ruff-lint, tree-sitter | ruff, black |
| Go | ✓ | lsp, go-vet, golangci-lint, tree-sitter | gofmt |
| Rust | ✓ | lsp, rust-clippy, tree-sitter | rustfmt |
| Ruby | ✓ | lsp, rubocop, tree-sitter | rubocop, standardrb |
| C/C++ | ✓ | lsp, cpp-check, tree-sitter | clang-format |
| Shell | ✓ | lsp, shellcheck | shfmt |
| Fish | ✓ (fish-lsp) | lsp, fish-indent | fish_indent |
| CSS/SCSS/Less | ✓ | lsp, stylelint | biome, prettier |
| HTML | ✓ | lsp, htmlhint | prettier (with project config) |
| YAML | ✓ | lsp, yamllint, actionlint (GitHub workflows), trivy-config (opt-in; Kubernetes manifests, CloudFormation) | prettier (with project config) |
| JSON | ✓ | lsp, trivy-config (opt-in; CloudFormation templates only) | biome, prettier |
| Svelte | ✓ | lsp | oxfmt (needs svelte pkg installed + config svelte: true) |
| Vue | ✓ | lsp | prettier, oxfmt |
| SQL | — | sqlfluff | sqlfluff |
| Markdown | — | spellcheck, markdownlint, vale | prettier |
| Docker | ✓ | lsp, hadolint, trivy-config (opt-in) | — |
| PHP | ✓ | lsp, php-lint, phpstan | php-cs-fixer |
| PowerShell | ✓ | lsp, psscriptanalyzer | psscriptanalyzer-format |
| Prisma | ✓ | lsp, prisma-validate | — |
| C# | ✓ | lsp, dotnet-build | csharpier |
| F# | ✓ | lsp | fantomas |
| Java | ✓ | lsp, javac | google-java-format |
| Java + Lombok | ✓ | JDT LS launched with -javaagent:<lombok.jar> when Lombok is detected and a jar is found (PI_LENS_LOMBOK_JAR / LOMBOK_JAR, project .lombok/lombok.jar, or Maven/Gradle cache) |
google-java-format |
| Kotlin | ✓ | lsp, ktlint, detekt | ktlint |
| Lean 4 | ✓ (Lake serve) |
lsp; uses each project's Lake toolchain and environment | — |
| Swift | ✓ | lsp, swiftlint | swiftformat |
| Dart | ✓ | lsp, dart-analyze | dart format |
| Lua | ✓ | lsp | stylua |
| Zig | ✓ | lsp, zig-check | zig fmt |
| Haskell | ✓ | lsp | ormolu |
| Elixir | ✓ (ElixirLS default, Expert alternate) | lsp, elixir-check, credo | mix format |
| Gleam | ✓ | lsp, gleam-check | gleam format |
| Typst | ✓ (tinymist) | — | typstyle (.typ, .typc) |
| OCaml | ✓ | lsp | ocamlformat |
| Clojure | ✓ | lsp | cljfmt |
| Terraform | ✓ | lsp, tflint, trivy-config (opt-in) | terraform fmt |
| Terragrunt | — | terragrunt | terragrunt hcl fmt |
| Nix | ✓ | lsp | nixfmt |
| TOML | ✓ | lsp, taplo | taplo |
| CMake | ✓ (cmake-language-server) | lsp | cmake-format |
| CUE | ✓ (syntax via cue lsp, evaluation via cue vet) | lsp, cue-vet | cue fmt |
cue lsp reports load and parse errors as you type but leaves conflicting
values and failed constraints to cue vet — the cue-vet auxiliary runner
(#1522) covers that gap, so together they give full coverage: syntax/parse
diagnostics, hover, definition, completion, code actions, and formatting from
the language server, plus evaluation-error validation from cue vet on every
edit (vetted at the PACKAGE level — the touched file's directory — with the
result filtered back to that file, since CUE packages are directory-scoped).
.cue files parse under tree-sitter with symbol (#Definitions, fields,
let bindings) and import queries (#1522), giving CUE the same structural
symbol search and import extraction as any other language, with two known
rough edges inherited from the young tree-sitter-cue grammar itself (not
this repo's queries):
- Multi-hash raw strings (
##"..."##, two or more#delimiters) mis-parse regardless of content — the field's value becomes a bareidentifiernode instead of astring, and the parser emits stray top-level nodes outside the field entirely. Because the broken value node can carry#-prefixed text, this can surface as a spurious symbol reference in the extracted refs. Single-hash raw strings (#"..."#) are unaffected. - Aliased field labels (
X=name: value) emit the alias identifier (X) as its own spuriouspropertysymbol alongside the real field's correct symbol, because the grammar exposes both identifiers as untagged siblings under the samelabelnode with no way to tell them apart structurally.
Both are upstream grammar limitations (tracked among eonpatapon/tree-sitter-cue's open issues), not something a query change here can fix.
The tree-sitter runner and symbol_search/module_report structural
extraction ship a fixed set of core grammars; grammars for the other
languages in the table above (CUE included) are fetched lazily on first use
into web-tree-sitter's resolved package directory, verified against
scripts/grammars.lock.json. This resolution works on a runtime that cannot
resolve a bare package specifier — pi shipped as a bun build --compile
binary — via one shared subpath-based ladder
(scripts/lib/web-tree-sitter-dir.mjs, #3409); before that fix, non-core
languages could never be analysed on those hosts. A grammar that never loads
(fetch failure, unwritable target, an unsupported runtime) is recorded as a
grammar_unavailable skip, never a clean pass
(clients/dispatch/runners/tree-sitter.ts).
Recorded so these are not re-litigated. The 2026-08-20 survey's first pass
rejected bandit and checkov as duplicates of existing lanes; #1752's
review disproved both "already covered" claims against the actual code, and
#1757 closed the resulting gaps:
- bandit (Python SAST) — RESOLVED. ruff's
Sruleset implements Bandit's checks;config/ruff/core.tomlnow selectsS, withS101(assert),S311(non-cryptographic random),S603/S607(subprocess withoutshell=True/ partial executable path) excluded as over-firing on real code (measured against therequestsandpippackages — see the config file's comments for the exclusion reasoning and hit counts).C90(mccabe complexity) was evaluated and NOT enabled: it is a maintainability metric, not a Bandit-equivalent security check, and its hit volume on a large pre-existing codebase (379 inpandas) would drown the security signalSadds. As always, a project-local ruff config overrides the bundled one outright. - checkov (IaC security) — the
trivy configlane pi-lens's own dispatch registry already carries (clients/dispatch/runners/trivy-config.ts, opt-in viatrivy.enabled) makes checkov a duplicate, not a gap: no second full-scan tool is needed. That lane covers Kubernetes manifests, Dockerfiles, and Terraform; #1757 added CloudFormation (yaml and json templates, detected viaAWSTemplateFormatVersion/ SAMTransform/Type: AWS::*heuristics) and fixed a real bug found while verifying against the installed binary: the runner passed--no-progresstotrivy config, a flag that subcommand rejects (onlytrivy fsaccepts it) — every real invocation exited 1 and, because trivy prints its usage text to stdout on a rejected flag, was misreported as a cleansucceededscan rather than an errored one. Dropping the flag was the fix. - radon / lizard (complexity) — still not enabled; see the C90 note above. Unchanged conclusion, now backed by measurement rather than assumption.
Known coverage holes with no tool currently clearing the adoption bar: Rust and Java dead-code detection, Ruby type checking (sorbet judged too heavy and idiosyncratic for a default lane).