Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

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 file check holds 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 each typed declaration 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's view lists 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. check and checkAll take opts.upstream to run the same check beside their own and report each module's outcome, and checkAll'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.luau form (declare statements and export type aliases), generated from the live registry and holding the globals the calling script has. upstreamCheck loads it into every module, so upstream checks a call against its signature and a member against its namespace. exportDefinitions(dir) writes zero.d.luau, zero.docs.json and .luaurc for an external editor running luau-lsp; the guide topics/external-editor walks through the editor setup. A *.d.luau file is checked by loading it as upstream does.