Skip to content

LuaU Globals

These are present in every script (preamble, handler, grouping) regardless of permissions.

APISignature
json.encode(value)any -> string
json.decode(s)string -> any (returns nil on parse error)

Tables with integer keys serialise as JSON arrays; tables with string keys serialise as objects. Mixed tables are not supported.

local node = json.decode(input)
if not node then error("invalid input") end
return json.encode({ source = "...", inline = {} })

These functions are registered by the engine and use stable opaque integer IDs (the _registryId field on the entity payload, or the result of a lookup).

FunctionSignatureDescription
get_node(id)int -> string|nilReturns the node’s full JSON, or nil.
find_struct_by_name(name)string -> int|nilReturns the registry ID of a struct by unqualified name (first match across all parsed sources).
struct_has_attribute(id, name)int, string -> bool|nilTrue if the struct carries [[codegen::name]] (or any namespaced attribute named name).
struct_has_method(id, name)int, string -> bool|nilTrue if the struct has a member function, static member function, or constructor with this name.
struct_has_member(id, name)int, string -> bool|nilTrue if the struct has a member or static member variable with this name.
struct_has_default_constructor(id)int -> bool|nilTrue if the struct has no constructors, or if any constructor’s parameters all have default values.
get_base_classes(id)int -> string|nilReturns a JSON array of {access, name, qualifiedName, id} for each base class. id is null when the analyzer can’t resolve the symbol.
get_definition_header(name)string -> string|nilReturns the path to the header that defines a given qualified or unqualified name, when known to the analyzer.

All functions return nil on bad arguments (wrong type or missing); only the lookup functions return nil for “not found.”

These functions expose the engine’s semantic model — type resolution, symbol lookup, references, and relations — to rules. They project the codex L2 SemanticQuery surface as JSON. Two ID conventions apply:

  • Node IDs are the same opaque registry integers used by the introspection API above (the _registryId on the payload, or the result of a lookup).
  • Symbol IDs are content hashes that cross the boundary as decimal strings (a 64-bit hash would lose precision as a LuaU number). Pass them back verbatim as strings.

Functions whose return type is json hand back a JSON string — call json.decode on it. The shape of every returned model is documented in Semantic Queries.

FunctionSignatureDescription
type_symbol_of(nodeId)int -> string|nilSymbol ID of the type of a node: a variable/field ⇒ its declared type; an expression ⇒ its inferred type; a type declaration ⇒ itself. nil for builtins, dependent/unresolved types, and non-type-bearing nodes.
type_node_of(nodeId)int -> int|nilRegistry node ID of the declaration of a node’s type — pivot from a traversed node straight into its type’s own AST. nil when the type has no user-declared symbol.
type_of(nodeId)int -> json|nilThe node’s full ResolvedType (state + type signature + resolved type symbol).
symbol_summary(symbolId)string -> json|nilFlat SymbolSummary for a symbol ID (kind, qualified name, signature, location, access, parent).
find_symbols(qualifiedName)string -> jsonJSON array of symbol-ID strings matching a qualified name (all overloads).
references(symbolId)string -> json|nilJSON array of Reference objects — every declaration, definition, and use site of the symbol, sorted by location.
base_classes(symbolId)string -> json|nilJSON array of the symbol’s direct base-class symbol-ID strings.
members(symbolId)string -> json|nilJSON array of the symbol’s direct member symbol-ID strings.

All of these — including the deepest facts (type_of, type_symbol_of, references) — resolve against a whole-workspace snapshot: workspace analysis runs unconditionally on every invocation, via a connection to the workspace daemon. Like the introspection API, every function returns nil on bad arguments or when the query has no answer.

Injected via the input parameter to preamble.luau:

{
"language": "cpp",
"outputFile": "path/to/output.cpp",
"ruleName": "RuleName",
"params": { /* rule.params */ },
"entities": [
{
"registryId": 42,
"qualifiedName": "app::MyStruct",
"structName": "MyStruct",
"namespaces": ["app"],
"inputFile": "include/app/my_struct.hpp",
"includes": ["<vector>"]
}
]
}

The entities array contains every entity bound to this output file. Use it to conditionally emit includes or other file-level content.

Injected via the input parameter to grouping.luau:

{
"params": { /* rule.params */ },
"entities": [
{
"registryId": 42,
"qualifiedName": "app::MyStruct",
"structName": "MyStruct",
"namespaces": ["app"],
"inputFile": "include/app/my_struct.hpp",
"defaultPath": "include/app/my_struct.g.cpp"
}
]
}

The defaultPath is the 1:1 output path (derived from the input header). Override it by returning a map from registryId to a new path.

Injected via the input parameter to transform.luau:

The full entity JSON from the codex AST, with these synthetic fields added:

  • _namespaces (array of strings, the qualifying namespace path)
  • _registryId (integer, stable identifier)
  • _outputPath (string, the post-grouping output file path)
  • params (object, copied from rule config)

The _outputPath is the final output file this entity will land in (after grouping, if present).

Available subset (the LuaU sandbox restricts the unsafe parts):

LibraryUse
tableinsert, remove, concat, sort, unpack
stringformat, find, gmatch, gsub, sub, len, rep, upper, lower
mathfloor, ceil, min, max, abs, huge, pi
Globalsipairs, pairs, tostring, tonumber, error, pcall, xpcall, type, select, assert, unpack
osopened as part of the base stdlib, then frozen along with every other global by luaL_sandbox
coroutineopened as part of the base stdlib; the engine also drives it internally for http.get, so don’t assume a script-created coroutine behaves independently of that

These appear only when the corresponding capability is requested via permissions: in the rule config (or via CLI overrides).

Enabled when permissions.http.allowlist is non-empty (or the CLI passes --allow-http).

APISignature
http.get(url)string -> string (response body), or yields (nil, error_string) on transport failure

The URL’s host component is matched against the allowlist (exact host, *.domain wildcard, or * as escape hatch — see permissions.http.allowlist). A URL whose host is not on the allowlist raises a runtime error and emits diagnostic E010. Concurrent requests on the same VM are not supported.

Enabled when permissions.env.os_allowlist is non-empty, or when any dotenv source is configured (per-rule .env, shared <rulesDir>/../shared/.env, or CLI --env KEY=VALUE).

The env module exposes the allowlisted OS env vars and the merged dotenv (CLI --env > per-rule .env > shared .env). See the Permissions page for the surface API.

Present only when components are installed, and only able to name one of them:

local typemap = require("acme/typemap")

The set of loadable modules is fixed before any script runs. codegen reads every component source from disk itself and hands the resulting map to the sandbox; require is a lookup in that map by exact string and is never joined to a path. There is no search path, no filesystem access, and no way to name anything the tool did not already read — a component that is not installed cannot be reached, however it is spelled.

Behaviour
ArgumentMust be a string. A component that is not installed raises an error naming what is.
ReturnWhatever the component returned. A component that returns nothing is an error.
Identityrequire twice in one run yields the same value; a fresh one per entity, matching how shared scripts have always behaved.
CyclesDetected and reported as a cycle, with the chain.
AvailabilityTransform, preamble and grouping scripts alike.

A tree with no components installed gets no require global at all.

See Rule Anatomy for the on-disk layout.

The following are explicitly not provided:

  • package, and any module search mechanism — require resolves only from the fixed set above
  • io (file I/O) — there is no filesystem module of any kind, and no fs capability
  • debug (Lua debug library; the DAP debugger is a separate feature, see VS Code DAP Debugger)
  • loadstring / loadfile
  • Any FFI

os.getenv reading an arbitrary OS variable is not the env capability — that’s the separate env module below, gated by os_allowlist.

Key Takeaways
  • json.encode / json.decode, the native introspection API, and the semantic query API are always present.
  • HTTP and env access are opt-in via permissions: in the rule config; nothing else opens the sandbox.
  • The native API gives scripts cross-entity lookup (base classes, definition headers, finding structs by name) without re-implementing the analyzer.
  • The semantic query API adds type resolution — resolve a node’s type to a symbol, pivot into that type’s declaration, and query references and relations. Symbol IDs are decimal strings; json-returning functions hand back a string to json.decode. See Semantic Queries.