Builtin Library
README.md
lsp Module
Public Luau surface over the __lsp Internal FFI namespace — the
engine-embedded Luau language server.
Purpose
Wrap the raw __lsp.* FFI namespace in a typed Luau table that gets
auto-injected as _G.lsp via the prelude. Same engine-embedded
language server that backs /zero/docs/api/* and the editor's
diagnostic surface — checks sources for diagnostics, enumerates
documented namespaces / methods / modules, infers expression types,
lists registered tools, and toggles the pre-execute strict gate.
Usage
-- Diagnostics
local diags = lsp.check("/zero/source/main.luau")
local diags2 = lsp.checkCode("local x = unknown_global", { severity = "error" })
local summary = lsp.checkAll({ scope = "user" })
-- Discovery
for _, ns in ipairs(lsp.namespaces()) do print(ns.name, ns.methodCount) end
for _, m in ipairs(lsp.methods("entity")) do print(m.signature) end
local doc = lsp.describe("asset/resolve")
for _, hit in ipairs(lsp.search("raycast")) do print(hit.path) end
-- Type inference
local td = lsp.typeOf("Transform.lookAt", "/zero/source/main.luau")
print(td.kind, td.display)
-- Strict gate
lsp.setStrictMode("soft")
print(lsp.getStrictMode())
What discovery answers with
namespaces, methods, search and describe answer with the surface
content calls. Most engine capabilities are registered as an FFI namespace at
ApiLevel::Internal — installed under __<name> — with a Luau module
published over it, and it is the module's surface a caller writes. So a
namespace holding no public entry is not listed, an entry whose signature
spells the __ call is left out of a listing, and one a module publishes at
globals/<ns>/<method> is listed once, at the published spelling. describe
answers an entry as registered, except where its signature spells the __
call and a module publishes over it — then the published entry answers.
{ includeInternal = true } on any of the four returns every registered
entry, exactly as registered:
lsp.methods("jobs") --> jobs.register(descriptor: table) -> JobHandle?
lsp.methods("jobs", { includeInternal = true }) --> also __jobs.register(descriptor) -> number
lsp.describe("jobs/register") --> the published entry
lsp.describe("jobs/register", { includeInternal = true }) --> the primitive's own
A namespace registered only internally — no module publishes over it — answers a listing request in full, so naming one deliberately still reports what it holds.
Reading a check result
Every check returns the diagnostics as an array plus the counts describing
them. A check that read no code — a path naming no file, a path holding no
Luau code body, or any path at all while lsp.enabled is false — returns one
error diagnostic carrying an lsp-check-* code, the path it reached, and the
reason:
local r = lsp.check("/zero/lib/modules/api/engine/lsp.module/init.luau")
print(r.errors) --> 1
print(r[1].code) --> lsp-check-unreadable
print(r[1].message) --> no code was checked: '…' returned no bytes from this VFS …
The code names which reason applied: lsp-check-unreadable for a path the VFS
returned no bytes for, lsp-check-unresolved for an argument that named no
file, lsp-check-no-code-body for one that named something with no Luau body,
lsp-check-not-utf8 for bytes that hold no source, lsp-check-no-path for a
call carrying no path, and lsp-check-disabled while lsp.enabled is false.
The checker analyses a source of at most 2 MiB (2097152 bytes). A larger file
is read and not analysed: every route that checks Luau (lsp.check,
lsp.checkCode, the diagnostics of the report vfs.write returns, the MCP
write_file result) answers it with one source-too-large warning naming its
size and the bound, lsp.check and lsp.checkCode report checked = false,
and the write itself lands as written. Splitting the file into modules under
the bound puts them back in front of the checker.
So errors > 0 means either the code has problems or nothing was read, and the
diagnostics say which. checked is true when the file's code was analysed, so
checked with errors == 0 means a code body was read and is clean.
checkedPath names the path the check reached.
Exports
- Diagnostics:
check,checkCode,checkAll,summary,checkDirty,lastCheckGen. - Discovery:
namespaces,methods,describe,search,modules,docsByKind. - Code-mode tools:
tools,describeTool. - Type inference:
typeOf. - Strict gate:
isStrict,setStrict,getStrictMode,setStrictMode,readDirectives. - Analysis configuration:
analysisFlags, the flag values upstream Luau's analysis library runs under, beside the ones upstream ships, with the reason for each one that differs. - Upstream type checking:
upstreamCheck, upstream Luau's own type checker run on a file and every module it requires, each require resolved to the filecheckholds it to (the file the runtime loads wherever it loads one), returned as data beside the engine's own checks. Upstream reads every module through its analysis view, in which eachtypeddeclaration reads as the plain Luau function it declares with its annotations kept and every line and column as in the file, so its findings point at the file as written; the result'sviewlists where the view of the checked file differs. The phases of a module's check whose work grows with the types they meet stop at its time limit, each module checked comes back with its outcome and duration, and the modules held between calls stay within a stated amount of source.checkandcheckAlltakeopts.upstreamto run the same check beside their own and report each module's outcome, andcheckAll's upstream pass counts its findings by kind. - The engine's API as Luau definitions:
definitions, the documented members, published aliases and asset-category shapes as upstream Luau's.d.luauform (declarestatements andexport typealiases), generated from the live registry and holding the globals the calling script has.upstreamCheckloads it into every module, so upstream checks a call against its signature and a member against its namespace.exportDefinitions(dir)writeszero.d.luau,zero.docs.jsonand.luaurcfor an external editor running luau-lsp; the guidetopics/external-editorwalks through the editor setup. A*.d.luaufile is checked by loading it as upstream does.